Round floats to N decimal places?


#1

I’m doing some math that results in floats with ~10 decimal places,
but I’d like to round them to 2 places. Is there a built in way of
doing this? Right now I’m doing format("%0.2f", the_float).to_f,
which seems to work fine but it seems like an ugly way of doing it.

Thanks,
Pat


#2

Pat M. wrote:

I’m doing some math that results in floats with ~10 decimal places,
but I’d like to round them to 2 places. Is there a built in way of
doing this? Right now I’m doing format("%0.2f", the_float).to_f,
which seems to work fine but it seems like an ugly way of doing it.

Thanks,
Pat

(the_float*100).round/100.0 is probably a bit faster… you might want
to check the benchmarks, though. Floats are inefficient in ruby, and the
string ops might actually be faster…

Oh what the heck, here’s the benchmark, and the winner is… strings!

This might be good place to use a C extension, if you really need it to
be fast.


require ‘benchmark’

n = 1000000
the_float = 1.0/7

raise unless ("%0.2f" % the_float).to_f == (the_float*100).round/100.0

Benchmark.bmbm(10) do |rpt|
rpt.report(“float rounding”) do
n.times {
("%0.2f" % the_float).to_f
}
end

rpt.report(“string rounding”) do
n.times {
(the_float*100).round/100.0
}
end
end

END

Output:

Rehearsal ---------------------------------------------------
float rounding 3.220000 0.000000 3.220000 ( 5.749314)
string rounding 2.240000 0.000000 2.240000 ( 4.452959)
------------------------------------------ total: 5.460000sec

                  user     system      total        real

float rounding 3.170000 0.010000 3.180000 ( 6.379592)
string rounding 2.210000 0.000000 2.210000 ( 4.422063)


#3

Joel VanderWerf wrote:

Oh what the heck, here’s the benchmark, and the winner is… strings!

Oops. I got the benchmark labels backwards. Strings are slower. Here’s
the correct code and output:

require ‘benchmark’

n = 1000000
the_float = 1.0/7

raise unless ("%0.2f" % the_float).to_f == (the_float*100).round/100.0

Benchmark.bmbm(10) do |rpt|
rpt.report(“float rounding”) do
n.times {
(the_float*100).round/100.0
}
end

rpt.report(“string rounding”) do
n.times {
("%0.2f" % the_float).to_f
}
end
end

END

Output:

Rehearsal ---------------------------------------------------
float rounding 2.210000 0.000000 2.210000 ( 2.884535)
string rounding 3.210000 0.000000 3.210000 ( 6.516337)
------------------------------------------ total: 5.420000sec

                  user     system      total        real

float rounding 2.190000 0.000000 2.190000 ( 4.398573)
string rounding 3.170000 0.010000 3.180000 ( 6.466687)


#4

Joel VanderWerf wrote:

Pat M. wrote:

I’m doing some math that results in floats with ~10 decimal places,
but I’d like to round them to 2 places. Is there a built in way of
doing this? Right now I’m doing format("%0.2f", the_float).to_f,
which seems to work fine but it seems like an ugly way of doing it.

This might be good place to use a C extension, if you really need it to
be fast.

Facets adds Numeric#round_at(d) and round_to(n).
http://facets.rubyforge.org/doc/api/classes/Float.html

Why not alter Ruby’s core Numeric#round to accept an extra parameter,
with
the same function as Facets’ round_at(d)? I’ve attached a patch
(untested)
below.

It seems to me it’s fully backwards-compatible, hardly slower, and not
at
all confusing. Round is a good name for this function, and it isn’t
incongruent with the existing function of that method.

Cheers,
Dave

Untested patch against 1.8.2:
— numeric.c~ Wed Mar 22 19:41:54 2006
+++ numeric.c Wed Mar 22 22:01:47 2006
@@ -1274,17 +1274,35 @@
flo_round(num)
VALUE num;
{

  • return flo_round2(0, NULL, num);
    +}

+static VALUE
+flo_round2(argc, argv, num)

  • int argc;
  • VALUE *argv;
  • VALUE num;
    +{
    double f = RFLOAT(num)->value;
    long val;
  • int dp;
  • double x;
  • if (f > 0.0) f = floor(f+0.5);
  • if (f < 0.0) f = ceil(f-0.5);
  • if (argc) {
  •   rb_scan_args(argc, argv, "01", &dp);
    
  •   x = pow(10.0, (double)dp);
    
  •   return rb_float_new((int)(num * x) / x);
    
  • }
  • else {
  •   if (f > 0.0) f = floor(f+0.5);
    
  •   if (f < 0.0) f = ceil(f-0.5);
    
  • if (!FIXABLE(f)) {
  •   return rb_dbl2big(f);
    
  •   if (!FIXABLE(f)) {
    
  •       return rb_dbl2big(f);
    
  •   }
    
  •   val = f;
    
  •   return LONG2FIX(val);
    
    }
  • val = f;
  • return LONG2FIX(val);
    }

/*
@@ -1372,6 +1390,15 @@
return flo_round(rb_Float(num));
}

+static VALUE
+num_round2(argc, argv, num)

  • int argc,
  •   VALUE *argv,
    
  • VALUE num;
    +{
  • return flo_round(argc, argv, rb_Float(num));
    +}

/*

  • call-seq:
  • num.truncate    => integer
    

@@ -2799,7 +2826,7 @@

 rb_define_method(rb_cNumeric, "floor", num_floor, 0);
 rb_define_method(rb_cNumeric, "ceil", num_ceil, 0);
  • rb_define_method(rb_cNumeric, “round”, num_round, 0);
  • rb_define_method(rb_cNumeric, “round”, num_round2, -1);
    rb_define_method(rb_cNumeric, “truncate”, num_truncate, 0);
    rb_define_method(rb_cNumeric, “step”, num_step, -1);

@@ -2819,7 +2846,6 @@
rb_define_method(rb_cInteger, “to_int”, int_to_i, 0);
rb_define_method(rb_cInteger, “floor”, int_to_i, 0);
rb_define_method(rb_cInteger, “ceil”, int_to_i, 0);

  • rb_define_method(rb_cInteger, “round”, int_to_i, 0);
    rb_define_method(rb_cInteger, “truncate”, int_to_i, 0);

    rb_cFixnum = rb_define_class(“Fixnum”, rb_cInteger);
    @@ -2913,7 +2939,7 @@
    rb_define_method(rb_cFloat, “to_int”, flo_truncate, 0);
    rb_define_method(rb_cFloat, “floor”, flo_floor, 0);
    rb_define_method(rb_cFloat, “ceil”, flo_ceil, 0);

  • rb_define_method(rb_cFloat, “round”, flo_round, 0);

  • rb_define_method(rb_cFloat, “round”, flo_round2, -1);
    rb_define_method(rb_cFloat, “truncate”, flo_truncate, 0);

    rb_define_method(rb_cFloat, “nan?”, flo_is_nan_p, 0);


#5

You should consider if you want to work with Float, Integer, BigDecimal
(which is some kind of LongFloat) or Rational. Using Float for
something that needs to be rounded regularly to a fixed number of
digits after the decimal point may or may not be the right way to go,
because it has some behaviour in terms of rounding which has to be
considered.
There is a RubyForge-project long-decimal (
http://rubyforge.org/projects/long-decimal/ ) which will be specialized
for doing calculations with a defined number of digits after the
decimal point. Maybe this will be useful for the kind of calculations
you are trying to do.