Certain floating point numbers have inherent inaccuracy from binary floating point representation:
> puts "%.50f" % (0.5) # cleanly representable
0.50000000000000000000000000000000000000000000000000
> puts "%.50f" % (0.1) # not cleanly representable
0.10000000000000000555111512312578270211815834045410
This is nothing new. But why does ruby's BigDecimal
also show this behaviour?
> puts "%.50f" % ("0.1".to_d)
0.10000000000000000555111512312578270211815834045410
(I'm using the rails shorthand .to_d
instead of BigDecimal.new
for brevity only, this is not a rails specific question.)
Question: Why is "0.1".to_d
still showing errors on the order of 10-17? I thought the purpose of BigDecimal
was expressly to avoid inaccuracies like that?
At first I thought this was because I was converting an already inaccurate floating point 0.1
to BigDecimal
, and BigDecimal
was just losslessly representing the inaccuraccy. But I made sure I was using the string constructor (as in the snippet above), which should avoid the problem.
EDIT:
A bit more investigation shows that BigDecimal
does still internally represent things cleanly. (Obvious, because otherwise this would be a huge bug in a very widely used system.) Here's an example with an operation that would still show error:
> puts "%.50f" % ("0.1".to_d * "10".to_d)
1.00000000000000000000000000000000000000000000000000
If the representation were lossy, that would show the same error as above, just shifted by an order of magnitude. What is going on here?
The %.50f
specifier takes a floating point value, so that decimal value needs to be converted to floating point before it's rendered for display, and as such is subjected to the same floating point noise you get in ordinary floating point values.
sprintf
and friends, like the String#%
method, do conversions automatically depending on the type specified in the placeholder.
To suppress that you'd have to use the .to_s
method on the BigDecimal number directly. It can take an optional format specifier if you want a certain number of places, and this can be chained in to a %s
placeholder in your other string.