summaryrefslogtreecommitdiff
path: root/numeric.c
diff options
context:
space:
mode:
authormarcandre <marcandre@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2011-08-31 04:13:00 +0000
committermarcandre <marcandre@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2011-08-31 04:13:00 +0000
commit64e0cae2741c501e8558966096b4b139d945ca82 (patch)
treebfb44bad91dabafc1c6aa911bdac028b42dad7c2 /numeric.c
parent800081d0c82fa24483d6485d7f0b067a3820151f (diff)
* numeric.c (flo_round): Avoid overflow by optimizing for trivial cases
[Bug #5227] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@33140 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'numeric.c')
-rw-r--r--numeric.c34
1 files changed, 28 insertions, 6 deletions
diff --git a/numeric.c b/numeric.c
index 96a3022e84..176b589b0b 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);