numeric.c: fix for small number

* numeric.c (flo_floor, flo_ceil): should not return zero for small
  number.  [ruby-core:81394] [Bug #13599]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@58913 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
nobu 2017-05-27 01:26:31 +00:00
parent 1dec75c02f
commit 9f5a468cf9
2 changed files with 57 additions and 31 deletions

View File

@ -168,7 +168,8 @@ static int int_round_zero_p(VALUE num, int ndigits);
VALUE rb_int_floor(VALUE num, int ndigits); VALUE rb_int_floor(VALUE num, int ndigits);
VALUE rb_int_ceil(VALUE num, int ndigits); VALUE rb_int_ceil(VALUE num, int ndigits);
static VALUE flo_to_i(VALUE num); static VALUE flo_to_i(VALUE num);
static int float_invariant_round(double number, int ndigits, VALUE *num); static int float_round_overflow(int ndigits, int binexp);
static int float_round_underflow(int ndigits, int binexp);
static ID id_coerce, id_div, id_divmod; static ID id_coerce, id_div, id_divmod;
#define id_to_i idTo_i #define id_to_i idTo_i
@ -1933,28 +1934,30 @@ static VALUE
flo_floor(int argc, VALUE *argv, VALUE num) flo_floor(int argc, VALUE *argv, VALUE num)
{ {
double number, f; double number, f;
long val;
int ndigits = 0; int ndigits = 0;
if (rb_check_arity(argc, 0, 1)) { if (rb_check_arity(argc, 0, 1)) {
ndigits = NUM2INT(argv[0]); ndigits = NUM2INT(argv[0]);
} }
if (ndigits < 0) {
return rb_int_floor(flo_to_i(num), ndigits);
}
number = RFLOAT_VALUE(num); number = RFLOAT_VALUE(num);
if (number == 0.0) {
return ndigits > 0 ? DBL2NUM(number) : INT2FIX(0);
}
if (ndigits > 0) { if (ndigits > 0) {
if (float_invariant_round(number, ndigits, &num)) return num; int binexp;
frexp(number, &binexp);
if (float_round_overflow(ndigits, binexp)) return num;
if (number > 0.0 && float_round_underflow(ndigits, binexp))
return DBL2NUM(0.0);
f = pow(10, ndigits); f = pow(10, ndigits);
f = floor(number * f) / f; f = floor(number * f) / f;
return DBL2NUM(f); return DBL2NUM(f);
} }
f = floor(number); else {
if (!FIXABLE(f)) { num = dbl2ival(floor(number));
return rb_dbl2big(f); if (ndigits < 0) num = rb_int_floor(num, ndigits);
return num;
} }
val = (long)f;
return LONG2FIX(val);
} }
/* /*
@ -2006,15 +2009,24 @@ flo_ceil(int argc, VALUE *argv, VALUE num)
ndigits = NUM2INT(argv[0]); ndigits = NUM2INT(argv[0]);
} }
number = RFLOAT_VALUE(num); number = RFLOAT_VALUE(num);
if (ndigits < 0) { if (number == 0.0) {
return rb_int_ceil(dbl2ival(ceil(number)), ndigits); return ndigits > 0 ? DBL2NUM(number) : INT2FIX(0);
} }
if (ndigits == 0) { if (ndigits > 0) {
return dbl2ival(ceil(number)); int binexp;
} frexp(number, &binexp);
if (float_invariant_round(number, ndigits, &num)) return num; if (float_round_overflow(ndigits, binexp)) return num;
if (number < 0.0 && float_round_underflow(ndigits, binexp))
return DBL2NUM(0.0);
f = pow(10, ndigits); f = pow(10, ndigits);
return DBL2NUM(ceil(number * f) / f); f = ceil(number * f) / f;
return DBL2NUM(f);
}
else {
num = dbl2ival(ceil(number));
if (ndigits < 0) num = rb_int_ceil(num, ndigits);
return num;
}
} }
static int static int
@ -2252,27 +2264,33 @@ flo_round(int argc, VALUE *argv, VALUE num)
ndigits = NUM2INT(nd); ndigits = NUM2INT(nd);
} }
mode = rb_num_get_rounding_option(opt); mode = rb_num_get_rounding_option(opt);
number = RFLOAT_VALUE(num);
if (number == 0.0) {
return ndigits > 0 ? DBL2NUM(number) : INT2FIX(0);
}
if (ndigits < 0) { if (ndigits < 0) {
return rb_int_round(flo_to_i(num), ndigits, mode); return rb_int_round(flo_to_i(num), ndigits, mode);
} }
number = RFLOAT_VALUE(num);
if (ndigits == 0) { if (ndigits == 0) {
x = ROUND_CALL(mode, round, (number, 1.0)); x = ROUND_CALL(mode, round, (number, 1.0));
return dbl2ival(x); return dbl2ival(x);
} }
if (float_invariant_round(number, ndigits, &num)) return num; if (isfinite(number)) {
int binexp;
frexp(number, &binexp);
if (float_round_overflow(ndigits, binexp)) return num;
if (float_round_underflow(ndigits, binexp)) return DBL2NUM(0);
f = pow(10, ndigits); f = pow(10, ndigits);
x = ROUND_CALL(mode, round, (number, f)); x = ROUND_CALL(mode, round, (number, f));
return DBL2NUM(x / f); return DBL2NUM(x / f);
} }
return num;
}
static int static int
float_invariant_round(double number, int ndigits, VALUE *num) float_round_overflow(int ndigits, int binexp)
{ {
enum {float_dig = DBL_DIG+2}; enum {float_dig = DBL_DIG+2};
int binexp;
frexp(number, &binexp);
/* Let `exp` be such that `number` is written as:"0.#{digits}e#{exp}", /* Let `exp` be such that `number` is written as:"0.#{digits}e#{exp}",
i.e. such that 10 ** (exp - 1) <= |number| < 10 ** exp i.e. such that 10 ** (exp - 1) <= |number| < 10 ** exp
@ -2291,12 +2309,16 @@ float_invariant_round(double number, int ndigits, VALUE *num)
So if ndigits + floor(binexp/(4 or 3)) >= float_dig, the result is number So if ndigits + floor(binexp/(4 or 3)) >= float_dig, the result is number
If ndigits + ceil(binexp/(3 or 4)) < 0 the result is 0 If ndigits + ceil(binexp/(3 or 4)) < 0 the result is 0
*/ */
if (isinf(number) || isnan(number) || if (ndigits >= float_dig - (binexp > 0 ? binexp / 4 : binexp / 3 - 1)) {
(ndigits >= float_dig - (binexp > 0 ? binexp / 4 : binexp / 3 - 1))) {
return TRUE; return TRUE;
} }
return FALSE;
}
static int
float_round_underflow(int ndigits, int binexp)
{
if (ndigits < - (binexp > 0 ? binexp / 3 + 1 : binexp / 4)) { if (ndigits < - (binexp > 0 ? binexp / 3 + 1 : binexp / 4)) {
*num = DBL2NUM(0);
return TRUE; return TRUE;
} }
return FALSE; return FALSE;

View File

@ -457,6 +457,8 @@ class TestFloat < Test::Unit::TestCase
end end
def test_floor_with_precision def test_floor_with_precision
assert_equal(+0.0, +0.001.floor(1))
assert_equal(-0.1, -0.001.floor(1))
assert_equal(1.100, 1.111.floor(1)) assert_equal(1.100, 1.111.floor(1))
assert_equal(1.110, 1.111.floor(2)) assert_equal(1.110, 1.111.floor(2))
assert_equal(11110, 11119.9.floor(-1)) assert_equal(11110, 11119.9.floor(-1))
@ -484,6 +486,8 @@ class TestFloat < Test::Unit::TestCase
end end
def test_ceil_with_precision def test_ceil_with_precision
assert_equal(+0.1, +0.001.ceil(1))
assert_equal(-0.0, -0.001.ceil(1))
assert_equal(1.200, 1.111.ceil(1)) assert_equal(1.200, 1.111.ceil(1))
assert_equal(1.120, 1.111.ceil(2)) assert_equal(1.120, 1.111.ceil(2))
assert_equal(11120, 11111.1.ceil(-1)) assert_equal(11120, 11111.1.ceil(-1))