Enter the 36 chambers of infrastructure wu-tang

Showing posts with label Ruby. Show all posts
Showing posts with label Ruby. Show all posts

Wednesday, March 26, 2008

BigDecimal: mostly acceptable alternative to Float

I forgot to mention in yesterday's extended whine about Ruby floating point that there is a package in the standard library called BigDecimal that is a mostly good alternative to the normal Float class.  Here is the simplest way to use it:

require "bigdecimal"
require "bigdecimal/util"

The second line adds a new method, to_d, to String, Float, and Rational.  Back to my annoying example of broken floating point math:

>> require "bigdecimal"
=> true
>> require "bigdecimal/util"
=> true
>> (9.54 / 0.001)
=> 9540.0
>> (9.54 / 0.001).to_i
=> 9539
>> (9.54 / 0.001).to_d.to_i
=> 9540

Well, almost.  BigDecimal slows things down a bit compared to Float and the extra work (the remembering part, not the typing part) of adding to_d is effort that shouldn't be required.  I should be clear here that I have no problem with BigDecimal itself.  BigDecimal is extremely useful, as are the extended precision libraries for other languages.  No, the problem remains that, even though the Ruby Core team could implement a reasonable compromise in Float so it behaves consistently, they refuse to.

rb_spread patch for Ruby 1.8.recent/1.9 and Spread 4.0

Spread is a group communication system, developed at Johns Hopkins, based on Virtual Synchrony and Extended Virtual Synchrony. Lots of neat stuff therein. The Ruby API hasn't been updated since 2005, though, and will not compile against recent versions of Ruby 1.8 or 1.9, nor is it compatible with the 4.0 release of Spread.

I put together this patch to update the API to the current goodness and make the test and sample programs actually work. My apologies for not doing lots of #ifdefs to make it work with both Spread 3.x and 4.x. I have only tested this on OS X 10.5 on i386. Success or failure, please let me know your experiences on other platforms.

Tuesday, March 25, 2008

Floating point arithmetic, bug reports, and monkey patching

It turns out that Barbie was right and math actually is hard. At least, math is hard if you adhere to IEEE floating point spec. C adheres to that spec. Languages built on C inherit that adherence and, even though they could fix some of the problems that come with it, they leave things broken.

In the case of Ruby, they also tell you there is no problem. And it is fixed in the next version. With this monkey patch. But I'm getting ahead of myself.

Here's Ruby (1.8.6 on OS X 10.5) in action:

>> (9.54 / 0.001)
=> 9540.0
>>

Well, that seems straightforward enough. 9540 is certainly the right answer. I need the integer representation, not the floating point version, though, so we'll just call the handy Float#to_i function:

>> (9.54 / 0.001).to_i
=> 9539
>>

Don't feel badly, I blinked several times and had a few more sips of beer before I realized what I was seeing. A second ago, the value was 9540, but now it is 9539. I'll just bang on it a bit to see what I've done wrong:

>> (9.54 / 0.001).to_s.to_i
=> 9540
>>

Yup. By converting the value to a String and then converting that String to an Integer we get the right value back. Sometimes Ruby admits to the underlying IEEE spec edge case, other times it hides it. Sadly, Python behaves similarly (though we are alerted to a problem immediately, rather than having it sneak up on us later):

>>> (9.54 / 0.001)
9539.9999999999982
>>>

Yikes! And, frowny face, the other trouble is here, too:

>>> str(9.54 / 0.001)
'9540.0'
>>> int(9.54 / 0.001)
9539
>>>

I wrote a simple C program to play directly with the underlying types. Here are two variants that illustrate the problem clearly:

#include

int
main()
{
  double f = 9.54 / 0.001;

  printf("%f %i\n", f, (int)f);

  return 0;
}

$ ./test
9540.000000 9539
$

#include

int
main()
{
  double f = 9.54 / 0.001;

  printf("%f %i\n", f, (int)(float)f);

  return 0;
}

$ ./test
9540.000000 9540
$

So, I filed a bug against Ruby. Turns out someone else filed a similar bug a few weeks ago. Both of our bugs were rejected on the grounds that things were working as they were supposed to. I continued to argue that, regardless of what the underlying data types are doing, Ruby could and should do better. We went back and forth a bit, and the Ruby gent finally noticed I filed the bug against 1.9.

Why does that matter? Well, it seems that, even though it totally, definitely, under no circumstances is a bug, they've added functionality to Float#round that, among other things, means it is possible to mostly work around the problem. You can now pass it an argument giving the rounding precision desired. So now we can do this (Ruby 1.9 on OS X 10.5):

irb(main):002:0> (9.54 / 0.001).round(2).to_i
=> 9540
irb(main):003:0>

Our grumpy Ruby Core gent was even kind enough to provide a monkey patch to Float to provide a truncate method that actually worked reliably:

class Float
  def truncate_rounding(figs)
    if figs > 0
      n = 10 ** figs
      (self * n).round / n
    else
      n = 10 ** -figs
      (self / n).round / 10 * (n * 10)
    end
  end
end

Since this is absolutely not a bug, the workaround (I can't call it a fix since it introduces problems with overflow) will no doubt remain a hack. A hack to make Float behave correctly. That's a darned shame, Ruby folks.