Forum: Ruby Round floats to N decimal places?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Pat M. (Guest)
on 2006-03-22 08:15
(Received via mailing list)
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
Joel VanderWerf (Guest)
on 2006-03-22 08:32
(Received via mailing list)
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)
Joel VanderWerf (Guest)
on 2006-03-22 09:10
(Received via mailing list)
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)
Dave B. (Guest)
on 2006-03-22 13:13
(Received via mailing list)
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);
unknown (Guest)
on 2006-03-28 01:09
(Received via mailing list)
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.
This topic is locked and can not be replied to.