Round floats to N decimal places?

P

Pat Maddox

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
 
J

Joel VanderWerf

Pat said:
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)
 
J

Joel VanderWerf

Joel said:
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)
 
D

Dave Burt

Joel said:
...
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);
 
K

karl_brodowsky

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,022
Latest member
MaybelleMa

Latest Threads

Top