Complete Ramblings

My thoughts (facts) on everything

Friday, September 28, 2007

When crap just doesn’t add up…

Math in programming just generally sucks. I come from a mainly Java background, and recently I’ve been using Ruby. But in both cases, the statement holds true. I know this isn’t really a new problem, but for god’s sake, it’s 2007; why is it still a problem?

Using Ruby, let’s suppose we have a simple math problem… and we want to check equality:

1.2 + 1.0 == 2.2 (returns true)

Yay, it’s correct! But that’s not really much to ask of a language is it now. Let’s switch it up just a little bit:

1.2 - 1.0 == 0.2 (returns false)

Uhh… Ruby? You OK? I really don’t understand how this is acceptable. I know the argument that we generally work and think in a base10 system, while computers generally store numbers in binary, and there’s no exact way to store many numbers. But shouldn’t that be fixed (and by fixed, I mean an easy way provided for us to perform exact base10 math), assuming once again, we generally work in base10? i.e. when I subtract 1 from 1.2, shouldn’t I get exactly 0.2?
Anyway, if you do a little research, you’ll find that BigDecimal is supposedly much more accurate when performing calculations, so let’s try that:

require 'bigdecimal'
BigDecimal.new('1.2') - BigDecimal.new('1') ==
   BigDecimal.new('0.2') (returns true)

Yay, it worked! …but who wants to type ‘BigDecimal.new’ a thousand times? Dig a little further and you’ll find a BigDecimal utility, which adds functionality to other Ruby Numeric classes to convert them to BigDecimals:

require 'bigdecimal'
require 'bigdecimal/util'
1.2.to_d - 1.0.to_d == 0.2.to_d (returns true)

Still works, and it’s definitely cleaner, but remembering to append ‘.to_d’ may be somewhat of a pain (and unnecessarily ugly.) The innaccuracy of Float can still bite you, however; check this out:

require 'bigdecimal'
require 'bigdecimal/util'
x = 1.2000000000000000001.to_d
puts x.to_s('F') (prints 1.2)

Yep, our 1 in the 1/1000000000000000000th place at the end is lost. This is probably because BigDecimal is aware of the innaccuracy of Float, and since that was of such small value, it assumed it was just a precision issue and is dropped. Frustrating!

Another issue for us might be that using BigDecimal in place of Float probably has more overhead; I don’t personally have any benchmarks to compare the two. But, then again, I don’t really need them. For me, being precise far outweighs it being fast (to a point of course). If I was performing some super-intensive process, that did crazy math, and as a result I only needed an answer that was pretty close but not exact, I’d probably give Float a chance. But until then, it’s BigDecimal for me, despite it not being the easist/cleanest/prettiest solution.

At any rate, I can live (for now) with implementing BigDecimal in this way (or ‘new’ing them up when appropriate.) But if you happen to use ActionWebService in Rails, you have another problem. Currently (as of version 1.2.3), when you define your API, and use the :float datatype, you’ll find this does in fact use Float to convey numbers. Dig a little, and you’ll find that in their SVN repository they may be working on changing this to use BigDecimal instead. One can only hope!

A coworker and I attempted to dig in and try to convert it ourselves for use in a current project, but that attempt was fairly short-lived, and we instead use the “.to_d” method above to convert them to BigDecimals once inside the service method. This works for us, since we’re generally dealing with dollar amounts that aren’t terribly huge, so we’re only concerned with accuracy to the penny.

I fully intend, at some point, to revisit this issue and perhaps instead of replacing :float to use BigDecimal, using :decimal or :bigdecimal, thus leaving the ability to use Float if so desired. Hopefully by the time I get to it, they’ll release a new version with it already implemented. I can dream can’t I?

posted by Justin at 8:07 pm  

7 Comments »

  1. If you choose to use BigDecimal, there’s another thing that you may want to know regarding display. To display a float, you may be familiar with the sprintf and/or format function(s). If you use these functions paired with a BigDecimal, the bad news is that it appears to convert the BigDecimal to Float (probably calls to_f), so you’re right back where you started. Instead, to_s on BigDecimal takes a parameter, either ‘E’ to display in Exponential notation (default), or ‘F’ to display in Float notation like you’re used to seeing.

    Comment by Justin — September 29, 2007 @ 10:55 am

  2. Is it really due to inaccuracy that 1.2 - 1.0 is not equal to 0.2? I’m tempted to conjecture that Ruby is transparently interpreting the 1.0 as an integer. Just a thought. Good post!

    Comment by Jim R. Wilson — October 9, 2007 @ 12:29 pm

  3. Nope, nevermind - it really is a problem. In Ruby, 1.2 - 1.0 - 0.2 gives you -5.55111512312578e-17

    Comment by Jim R. Wilson — October 9, 2007 @ 12:44 pm


  4. x = 1.2000000000000000001.to_d

    I think what’s actually happening here is, Ruby converts the literal number into a float. The float can’t represent the 1 at the end so its lost. Then to_d is called and never sees the 1 at the end.

    I remember using Borland compilers back in the 90s, and they supported BCD (Binary Coded Decimal). This was the recommended format for financial calculations, but it was slower and slightly wasteful of memory compared to binary floats. Still I’d wager its faster/smaller than BigDecimal (unless the latter is implemented in the former)

    Comment by Spacebat — October 10, 2007 @ 3:41 pm

  5. x = 1.00000000000001.to_d
    => #

    puts x.to_s(’f')
    => 1.00000000000001

    It seems that 14 places is the limit.

    x = 1.000000000000001.to_d
    => #
    puts x.to_s(’f')
    => 1.0

    Going to the 15th drops the resolution.

    Comment by Fu — October 12, 2007 @ 7:36 am

  6. Unless I’m dumb, isn’t string the answer?

    x = “1.00000000000000000000000000000000001″.to_d
    => #
    puts x.to_s(”F”)
    “1.00000000000000000000000000000000001″

    Comment by Julian Burgess — June 12, 2008 @ 7:31 am

  7. bextra…

    bula do bextra…

    Trackback by montana bextra attorney — July 30, 2008 @ 2:20 pm

RSS feed for comments on this post. TrackBack URI

Leave a comment

Powered by WordPress