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[0].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
end
end

describe "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!

No comments: