28 September 2007

Playing with float arithmetics with Ruby: how to truncate decimals

I needed to truncate a float to 4 decimals because I am using geocoding in my app and Google map returns coordinates with a lot of decimals ...

After reading this post in the ruby-talk mailing list, I decided to take a break in my development and write the following code :

`require 'benchmark'class Float  def truncate_decimals_with_printf(number_of_decimals_after_dot)    ("%.#{number_of_decimals_after_dot}f" % self).to_f  end    def truncate_decimals_with_regexp(number_of_decimals_after_dot)    match = Regexp.compile("\\d*." + "\\d" * number_of_decimals_after_dot).match(self.to_s)    match.to_f  end    def truncate_decimals_with_arithmetic(number_of_decimals_after_dot)    x = 10 ** number_of_decimals_after_dot    (self * x).round.to_f / x  endenddescribe "Float.truncate_decimals" do    it "could truncate decimals using printf" do    1.12349.truncate_decimals_with_printf(4).should == 1.1234  end    it "could truncate decimals using a regexp" do    1.12349.truncate_decimals_with_regexp(4).should == 1.1234  end    it "could truncate decimals using arithmetic" do    1.12349.truncate_decimals_with_arithmetic(4).should == 1.1234  end    it "Benchmark:" do        Benchmark.bm(10) do |timer|      timer.report('arithmetic') { 1.12349.truncate_decimals_with_arithmetic(4) }      timer.report('printf') { 1.12349.truncate_decimals_with_printf(4) }      timer.report('regexp') { 1.12349.truncate_decimals_with_regexp(4) }          end      end  end`

The result:

Float.truncate_decimals
- could truncate decimals using printf (FAILED - 1)
- could truncate decimals using a regexp
- could truncate decimalsusing arithmetic (FAILED - 2)
user system total real
arithmetic 0.000000 0.000000 0.000000 ( 0.000015)
printf 0.000000 0.000000 0.000000 ( 0.000017)
regexp 0.000000 0.000000 0.000000 ( 0.000028)
- Benchmark:

1)
'Float.truncate_decimals could truncate decimals using printf' FAILED
expected: 1.1234,
got: 1.1235 (using ==)
/home/jeanmichel/ruby/projects/iss2.0/spec/models/float_spec.rb:22:

2)
'Float.truncate_decimals could truncate using arithmetic' FAILED
expected: 1.1234,
got: 1.1235 (using ==)
/home/jeanmichel/ruby/projects/iss2.0/spec/models/float_spec.rb:30:

Finished in 0.008598 seconds

4 examples, 2 failures

The arithmetric solution is the fastest! Faster than printf ! Computers are good at division and multiplication ;-) The regexp is damned slow, however it's the only one which actually fullfill the specs ;-)

Any idea how to change other implementations ?

Today, I have lost a few hours because the arithmetic implementation was working on my dev box, a Linux 2.6.20-16-386 i686 GNU with a ruby 1.8.5 (2006-08-25) [i486-linux] and NOT at all on my production server, a
Linux i686 i686 i386 GNU/Linux with a ruby 1.8.4 (2005-12-24) [i386-linux] ...

At the end, I used the printf version, which is OK because I don't care about the rounding...

Most important rule: you should always get a stage server IDENTICAL to your production server and run the tests / specs on it before deploying!