dangerous float operations
Reported by Tomasz | July 7th, 2008 @ 03:10 PM | in 2.x
While implementing storage of price/cost/balance in cents I've stumbled upon one nasty bug. Consider the following code:
class Account < ActiveRecord::Base
def balance=(amount)
write_attribute(:balance, amount.to_f*100.0)
end
end
(analogous accessor code omitted)
It works as expected... usually. That is, there's one magical number - 4.6 - which sets the field balance not to 460, but to 459, thus screwing up the whole purpose of this.
4.6 is the only magical number I could find - others work like charm and expected.
In Ruby (not on Rails) works as expected:
class Act
def balance=(amount)
@balance = amount.to_f*100
end
end
that is, for 4.6 works like for all the other numbers.
EDIT: This problem seems to be related to to_i method called on floats. AND it seems to be Ruby-related
irb(main):005:0> (895.0).to_i
=> 895
irb(main):006:0> (8.95*100).to_i
=> 894
(yes, I've just discovered that to_i just cuts away everything after the point)
Workaround: add a tiny number to the converted one OR use .round
irb(main):007:0> (8.95*100 + 0.0000001).to_i
=> 895
irb(main):007:0> (8.95*100).round
=> 895
Proposed solution: make field accessors for integer fields use .round instead of to_i.
As for now, I'm good with adding .round in my custom accessor.
Comments and changes to this ticket
-

Tomasz July 7th, 2008 @ 03:37 PM
Update: 8.95 is also a "magical number" inside ActiveRecord, i.e. balance=8.95 sets balance field to 894. Other numbers "around" seem OK.
>> a.balance=(8.95) => 8.95 >> a.balance => 894 >> a.balance=(8.93) => 8.93 >> a.balance => 893 -

isak August 7th, 2008 @ 01:23 PM
There is no bug, floating point numbers are inaccurate by design.
The only reason people muck around with cents for money values is to avoid the floating point issue. This approach really hasn't been viable since 1.2, when ActiveRecord finally got support for decimal columns, but it seems like many pick it up from outdated tutorials floating around on the web.
-
Jim Lindley August 9th, 2008 @ 09:58 PM
Tomasz,
There's more info on the details here, but isak is right - storage of floating point numbers in binary representation is less accurate then you'd think.
-

Frederick Cheung August 10th, 2008 @ 12:18 AM
A classic paper on this is http://docs.sun.com/source/806-3...
-

Tomasz August 10th, 2008 @ 04:32 PM
I know, I know. We're using INT columnt because of that and convert numbers on-the-fly by overloading amount and amount= methods in our models. But I've just realised it's a bad approach and we should just represent them as ints also internally, doing the "conversion" only on the view level.
Sorry for the hassle, You can close it as invalid/wontfix/reporter_incompetent ;)
-
Tarmo Tänav September 18th, 2008 @ 05:53 AM
- → Tag changed from activerecord bug to activerecord bug
- → State changed from new to invalid
Please Login or create a free account to add a new comment.
You can update this ticket by sending an email to from your email client. (help)
Create your profile
Help contribute to this project by taking a few moments to create your personal profile. Create your profile »
Source available from github
The Git repository resides at http://github.com/rails
Check out the current development trunk (Edge Rails) with:
git clone git://github.com/rails/rails.git
Creating or reviewing a patch
See the contributor guide.
Creating a feature request
Please don't. If you want a new feature in Rails, you'll have to pull up your sleeves and get busy yourself. Or convince someone else to do it. See the contributor guide on how to get going. But posting them here is just going to lead to ticket root.
Creating a bug report
When creating a bug report, be sure to include as much relevant information as possible. Post the code sample that causes the problem. Preferably, alter the unit tests and show through either changed or added tests how the expected behavior is not occuring.
Security vulnerabilities should be reported via an email to security@rubyonrails.org, do not use trac for reporting security vulnerabilities. All content in trac is publicly available as soon as it is posted.
Then don't get your hopes up. Unless you have a "Code Red, Mission Critical, The World is Coming to an End" kinda bug, you're creating this ticket in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the ticket automatically will see any activity or that others will jump to fix it. Creating a ticket like this is mostly to help yourself start on the path of fixing the problem and for others to sign on to with a "I'm having this problem too".
