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?
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 ;-)
Use BigDecimal: BigDecimal.new("1.025").round(2)
=> "1.03"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With