views:

1474

answers:

5

Hi, I am writing code that will deal with currencies, charges, etc.. I am going to use the BigDecimal class for math & storage.

We ran into something weird with it, however.

Using this statement:

 1876.8 == BigDecimal('1876.8')

it returns false.

If I run those values through a formatting string "%.13f" I get:

"%.20f" % 1876.8 => 1876.8000000000000
"%.20f" % BigDecimal('1876.8') => 1876.8000000000002

Note the extra 2 from the BigDecimal at the 13th decimal place.

I thought BigDecimal was supposed to counter the inaccuracies of storing real numbers directly in the native floating point of the computer. Where is this 2 coming from?

+4  A: 

You are right, BigDecimal should be storing it correctly, my best guess is:

  • BigDecimal is storing the value correctly
  • When passed to a string formatting function, BigDecimal is being cast as a lower precision floating point value, creating the ...02.
  • When compared directly with a float, the float has an extra decimal place far beyond the 20 you see (classic floats can't be compared behavoir).

Either way, you are unlikely to get accurate results comparing a float to a BigDecimal.

David
This was my feeling as well. Thanks!
Tilendor
+3  A: 

as David said, BigDecimal is storing it right

 p (BigDecimal('1876.8') * 100000000000000).to_i

returns 187680000000000000

so, yes, the string formatting is ruining it

Maximiliano Guzman
+1  A: 

On Mac OS X, I'm running ruby 1.8.7 (2008-08-11 patchlevel 72) [i686-darwin9]

 irb(main):004:0> 1876.8 == BigDecimal('1876.8') => true 

However, being Ruby, I think you should think in terms of messages sent to objects. What does this return to you:

BigDecimal('1876.8') == 1876.8

The two aren't equivelent, and if you're trying to use BigDecimal's ability to determine precise decimal equality, it should be the receiver of the message asking about the equality. For the same reason I don't think formatting the BigDecimal by sending a format message to the format string is the right approach either

AdminMyServer
I'm running Windows XP, ruby 1.8.6 (2007-09-24 patchlevel 111) [i386-mswin32]. The order doesn't effect the comparison, but BigDecimal('1876.8') == 1876.8.to_d is true.(to_d converts to BigDecimal).The formatting was the only way I could find a difference between the values. I agree it shouldn't be used for other preferences.
Tilendor
+1  A: 

If you don't need fractional cents, consider storing and manipulating the currency as an integer, then dividing by 100 when it's time to display. I find that easier than dealing with the inevitable precision issues of storing and manipulating in floating point.

Sarah Mei
I've considered that. However, we are going to be interested in supporting other currencies. That means we can't rely upon cents being the proper unit.
Tilendor
Right, if you want to do multiple currencies you'd need to store a conversion unit - how many Xs to the Y - then use that at display time instead of 100.
Sarah Mei
+2  A: 

It won't give you as much control over the number of decimal places, but the conventional format mechanism for BigDecimal appears to be:

a.to_s('F')

If you need more control, consider using the Money gem, assuming your domain problem is mostly about currency.

gem install money
JasonTrue