Monday, December 29, 2014

Casting Floats to BigDecimal in Ruby or Ruby on Rails

Rails requires 'bigdecimal' by default it seems, so only in (plain) Ruby (off Rails) do you need:

2.1.2 :001 > require 'bigdecimal'

In both Ruby and Ruby on Rails (though the stack is different):

2.1.2 :002 > BigDecimal.new(0.2342334)
ArgumentError: can't omit precision for a Float.
...

Some might be inclined to specify the number of significant digits as nil/0, such that the number of significant digits is determined from the initial value. But that leads to the following in Rails Console:

2.1.1 :006 > BigDecimal.new(0.2342334, 0)
 => #<BigDecimal:10bad84f8,'0.2342334000 0000000826 7520001936 645713E0',36(54)>
2.1.1 :008 > BigDecimal.new(0.2342334, 0).to_s
 => "0.234233400000000008267520001936645713"

Note: in irb/vanilla Ruby, to_s includes "E0" at the end:

2.1.2 :003 > BigDecimal.new(0.2342334, 0).to_s
 => "0.234233400000000008267520001936645713E0"

A workaround is to:

2.1.1 :007 > (BigDecimal.new(1) * 0.2342334).to_s
 => "0.2342334"

I've not looked into this, but I think what is going on is that to_s on Float in Ruby tosses some of the digits in this case that might be including the floating-point inaccuracy, and that when you multiply BigDecimal it may be using to_s and using that string representation of the Float as the value for the BigDecimal constructor.

No comments: