3

Two different rounding methods of floats show different results.

"%.2f" % 0.015 # => "0.01" 
0.015.round(2) # => 0.02 

One is string and the other a float. When rounding anything but 0.5, it rounds correctly or rather the same way as the round function.

"%.2f" % 0.01500000000000001 # => "0.02" 

Also, it doesn't always behave like that:

[0.005, 0.015, 0.025, 0.035, 0.045, 0.055, 0.065, 0.075, 0.085, 0.095].map { |x| "%.2f" % x}
# => ["0.01", "0.01", "0.03", "0.04", "0.04", "0.06", "0.07", "0.07", "0.09", "0.10"]

I am not sure if this is technically a bug, but at least it is very counter intuitive. Does anybody know why two rounding methods act that differently?

sawa
  • 165,429
  • 45
  • 277
  • 381
Lapis
  • 83
  • 6
  • 1
    Which [rounding method](https://en.wikipedia.org/wiki/Rounding) are you looking to find? If you use a consistent method for rounding you'll get consistent results. – tadman Jun 17 '14 at 16:14
  • In one case I needed it to stay a float, in the other I needed a string instead. Having two different results was counter intuitive and caused some bad bugs. – Lapis Jun 17 '14 at 16:18
  • It's not a bug, but an artifact of how crazy floating point numbers can be since some values cannot be represented accurately, only approximated. It's a lot safer to use fixed point math, or something like BigDecimal that's designed to be slower but more consistent. – tadman Jun 17 '14 at 16:25
  • I'm not a ruby expert, but searching some ruby documentation for sprintf, I see nothing that guarantees sprintf implements rounding as part of printing floats at a specified precision. Perhaps this is the problem. – statueuphemism Jun 17 '14 at 16:27
  • possible duplicate of [Is floating point math broken?](http://stackoverflow.com/questions/588004/is-floating-point-math-broken) – tmyklebu Jun 17 '14 at 18:09
  • @tadman: Binary isn't "crazy." – tmyklebu Jun 17 '14 at 18:10
  • @tmyklebu It is pretty crazy when there's a number of different representations for the same numerical value, and they can't be compared for equivalence, among other things. Floating point has to be presumed to be an approximation, not a literal representation. – tadman Jun 17 '14 at 19:27
  • 2
    @tadman: There's one representation for every floating-point number except zero. You can tell the two signed zeroes apart (if you ever need to) by comparing their reciprocals to zero. Binary floating-point is great at representing certain values exactly. There is no reason to presume anything's approximate when it isn't. – tmyklebu Jun 17 '14 at 20:24
  • The question and answers seem to be focusing on the behaviour of the `"%.2f"` formatting. However, technically it's the `round` method that's "wrong" here. The value stored internally for the literal `0.015` is actually a touch less than `0.015` (it's 0.01499999999999999944488848768742172978818416595458984375, to be exact), so if anything the rounding operation should round *down* to `0.01`. Ruby is using a somewhat naive rounding algorithm which is not correctly rounded. (N.B. That doesn't make it a bug, unless Ruby's docs claim to do correct rounding here.) – Mark Dickinson Jun 18 '14 at 07:14
  • As to the formatting: I haven't checked, but it's likely that Ruby is simply using the OS-supplied functionality (`dtoa`, `sprintf` and the like) for this. Typically, the results of that *will* be correctly rounded, modulo obscure corner cases and OS library bugs. – Mark Dickinson Jun 18 '14 at 07:16

1 Answers1

3

Floating values aren't always stored exactly -- they are instead stored as a base and exponent which for some numbers is stored exactly and others is not. Any modifications to the float can cause a very small fraction to appear. Thus rounding something right at a boundary with different rounding functions can make it switch between the two possible results.

Doing "%.2f" % 0.015.round(2) will give you the 0.02 result you wanted, as I am guessing the %.2f is implemented differently than the floating round method.

Pyrce
  • 8,296
  • 3
  • 31
  • 46
  • There's a lot of tradition behind how `%` and `sprintf`-like operations should behave, that inherits from the implementation in C. Could be that it's just implementing that behaviour consistently. – tadman Jun 17 '14 at 16:26
  • That's very likely - it's also possible that %.2f assumes a certain byte-size float/double which differs from the actual type used by particular ruby interpreters and a cast to match the sprintf/C type is done. – Pyrce Jun 17 '14 at 16:32
  • I am using "%.2f" % 0.015.round(2) for now as a work around. The real problem IMO is the unexpected difference between the two methods though. – Lapis Jun 17 '14 at 16:37
  • 1
    Well one is a coercion to string, the other is a coercion to another float (which is then coerced to a string for printing to console) so technically the two methods are not equivalent logically speaking. – Pyrce Jun 17 '14 at 16:40