Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby: Why is 1.025.round(2) rounded to 1.02?

As far as I understand the .round()-functionality in ruby rounds decimals upwards where the last significant number is 5?

For example 1.5.round(0) # => 2 (OK)

but why does 1.025.round(2) # => 1.02, and not 1.03 as I would expect?

irb(main):037:0> 1.025.round(2)
=> 1.02

What can I do to go around this?

like image 392
Christoffer Avatar asked Sep 29 '11 08:09

Christoffer


1 Answers

This has nothing to do with the last digit being 5 and everything to do with conversion of a decimal value to a double precision floating point value.

http://en.wikipedia.org/wiki/Double_precision_floating-point_format

Basically, the decimal number has to be represented in limited binary format which can only approximate certain decimal values, leading to loss of precision. This can cause some weird behavior, as you have seen.

Best to explain this by showing you... Marshal.dump(1.025) dumps the Float value and shows the value a bit closer to what it really is: 1.0249999999999999. 1.025.to_r will provide you with the fraction which represents the binary value. You can use the arbitrarily precise decimal library, BigDecimal to convert this:

ruby-1.9.2-p180 :060 > (BigDecimal.new("2308094809027379.0") / BigDecimal.new("2251799813685248.0")).to_s('F')
=> "1.024999999999999911182158029987476766"

When certain decimals are converted to this "approximate" binary number format they will be represented differently, possibly more precisely. So, you might have noticed that 1.085.round(2) results in 1.09 as you'd expect.

This lack of precision with floating point math means it's never, ever appropiate to use floating point values for currency calculations, or even as temporary containers for money values. Arbitrary precision data types should be used at all times for anything involving money.

As a former developer for an extremely large financial company I was constantly shocked by how rarely this advice is heeded and how common the use of floats or doubles is in financial software. Most programmers in that industry I have talked to are not aware that floats and doubles should never store money values. So, don't feel like you are too behind the curve ;-)

tl;dr

Use BigDecimal: BigDecimal.new("1.025").round(2) => "1.03"

like image 139
Carl Zulauf Avatar answered Sep 29 '22 20:09

Carl Zulauf