summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog6
-rw-r--r--numeric.c34
2 files changed, 34 insertions, 6 deletions
diff --git a/ChangeLog b/ChangeLog
index b8fcf41b79..f65f54e999 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+Wed Aug 31 13:19:31 2011 Marc-Andre Lafortune <ruby-core@marc-andre.ca>
+
+ * numeric.c (flo_round): Avoid overflow by optimizing for trivial
+ cases
+ [Bug #5227]
+
Wed Aug 31 06:45:32 2011 KOSAKI Motohiro <kosaki.motohiro@gmail.com>
* configure.in: fix r32835. $withval can't be used outer AC_ARG_WITH().
diff --git a/numeric.c b/numeric.c
index ab890c6153..4a94a95608 100644
--- a/numeric.c
+++ b/numeric.c
@@ -1491,18 +1491,40 @@ flo_round(int argc, VALUE *argv, VALUE num)
VALUE nd;
double number, f;
int ndigits = 0;
+ int binexp;
long val;
if (argc > 0 && rb_scan_args(argc, argv, "01", &nd) == 1) {
ndigits = NUM2INT(nd);
}
number = RFLOAT_VALUE(num);
- f = pow(10, abs(ndigits));
-
- if (isinf(f)) {
- if (ndigits < 0) number = 0;
- }
- else {
+ frexp (number , &binexp);
+
+/* Let `exp` be such that `number` is written as:"0.#{digits}e#{exp}",
+ i.e. such that 10 ** (exp - 1) <= |number| < 10 ** exp
+ Recall that up to 17 digits can be needed to represent a double,
+ so if ndigits + exp >= 17, the intermediate value (number * 10 ** ndigits)
+ will be an integer and thus the result is the original number.
+ If ndigits + exp <= 0, the result is 0 or "1e#{exp}", so
+ if ndigits + exp < 0, the result is 0.
+ We have:
+ 2 ** (binexp-1) <= |number| < 2 ** binexp
+ 10 ** ((binexp-1)/log_2(10)) <= |number| < 10 ** (binexp/log_2(10))
+ If binexp >= 0, and since log_2(10) = 3.322259:
+ 10 ** (binexp/4 - 1) < |number| < 10 ** (binexp/3)
+ binexp/4 <= exp <= binexp/3
+ If binexp <= 0, swap the /4 and the /3
+ So if ndigits + binexp/(4 or 3) >= 17, the result is number
+ If ndigits + binexp/(3 or 4) < 0 the result is 0
+*/
+ if (isinf(number) || isnan(number)) {
+ /* Do nothing */
+ }
+ else if ((long)ndigits * (4 - (binexp > 0)) + binexp < 0) {
+ number = 0;
+ }
+ else if (((long)ndigits - 17) * (3 + (binexp > 0)) + binexp < 0) {
+ f = pow(10, abs(ndigits));
if (ndigits < 0) {
double absnum = fabs(number);
if (absnum < f) return INT2FIX(0);