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

on 2006-03-22 07:15

on 2006-03-22 07:32

Pat Maddox 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)

on 2006-03-22 08:10

```
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)
```

on 2006-03-22 12:13

Joel VanderWerf wrote: > Pat Maddox 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);

on 2006-03-27 23:09

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.