Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

dangerous float operations

#564

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.

Reported by Tomash · July 7th, 2008 @ 03:46 PM

State: invalid
Milestone: 2.x
Assigned to: nobody
Importance: none

Activity

  1. Tomash
    Tomash

    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
    

    July 7th, 2008 @ 03:37 PM

  2. isak
    isak

    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.

    August 7th, 2008 @ 01:23 PM

  3. Jim Lindley
    Jim Lindley

    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.

    August 9th, 2008 @ 09:58 PM

  4. Frederick Cheung
  5. Tomash
    Tomash

    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 ;)

    August 10th, 2008 @ 04:32 PM

  6. Tarmo Tänav
    Tarmo Tänav
    • State changed from new to invalid
    • Tag changed from activerecord, bug to activerecord, bug

    September 18th, 2008 @ 05:53 AM