In my previous entry I had some takers on trying to write a nice, compact, method that handled round a float down to a certain decimal place.

For fun, I decided to benchmark each implementation, along with a small C implementation I wrote. Here’s the lineup:

require 'benchmark'
require 'prec_c'
include Benchmark

class Float
  def prec_caleb1(x)
    sprintf("%.0" + x.to_i.to_s + "f", self).to_f
  end

  def prec_caleb2(x)
    (("%.0" + x.to_i.to_s + "f") % self).to_f
  end

  def prec_james(x)
    ("%.0#{x.to_i}f" % self).to_f
  end

  def prec_jonas(x)
    (self * 10**x).to_i / (10**x).to_f
  end

  def prec_fansipans(x)
    to_s[/.*..{#{x}}/]
  end
end

n = 50000
bm do |x|
  r = rand(6)
  x.report { for i in 1..n; 5.44235102.prec_c(r); end; }
  x.report { for i in 1..n; 5.44235102.prec_caleb1(r); end; }
  x.report { for i in 1..n; 5.44235102.prec_caleb2(r); end; }
  x.report { for i in 1..n; 5.44235102.prec_james(r); end; }
  x.report { for i in 1..n; 5.44235102.prec_jonas(r); end; }
  x.report { for i in 1..n; 5.44235102.prec_fansipans(r); end; }
end

I ran the code a couple of times to make sure the results came up rough the same. They did. Here are the results (sorry for the formatting):

impl user system total real
caleb_c 0.330000 0.000000 0.330000 ( 0.325121)

caleb1 0.450000 0.000000 0.450000 ( 0.454775)

caleb2 0.460000 0.000000 0.460000 ( 0.456334)

james 0.410000 0.000000 0.410000 ( 0.405698)

jonas 0.480000 0.000000 0.480000 ( 0.486741)

fansipans 0.950000 0.000000 0.950000 ( 0.944716)

It looks like James is the winner, at least in terms of efficiency (as long as you don’t count my C extension, which kind of breaks the Ruby spirit).

Here’s that code, in case you’re interested:

#include "ruby.h"
#include <signal.h>
#include <time.h>

static VALUE prec_c(VALUE klass, VALUE prec)
{
  VALUE str = rb_str_plus( rb_str_new2("%."), rb_funcall(prec, rb_intern("to_s"), 0) );
  VALUE str2 = rb_str_plus( str, rb_str_new2("f") );

  char s[20];

  sprintf(s, StringValuePtr(str2), NUM2DBL(klass) );
  return rb_funcall(rb_str_new2(s), rb_intern("to_f"), 0);
}

void Init_prec_c() {
  rb_define_method(rb_cFloat, "prec_c", prec_c, 1);
}

I’m sure someone can find some fallacies in my code that makes for unfairness in my benchmarking, but for the most part I think the data is fairly reliable.