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
Родитель 1dec75c02f
Коммит 9f5a468cf9
2 изменённых файлов: 57 добавлений и 31 удалений

Просмотреть файл

@ -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_ceil(VALUE num, int ndigits);
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;
#define id_to_i idTo_i
@ -1933,28 +1934,30 @@ static VALUE
flo_floor(int argc, VALUE *argv, VALUE num)
{
double number, f;
long val;
int ndigits = 0;
if (rb_check_arity(argc, 0, 1)) {
ndigits = NUM2INT(argv[0]);
}
if (ndigits < 0) {
return rb_int_floor(flo_to_i(num), ndigits);
}
number = RFLOAT_VALUE(num);
if (number == 0.0) {
return ndigits > 0 ? DBL2NUM(number) : INT2FIX(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 = floor(number * f) / f;
return DBL2NUM(f);
}
f = floor(number);
if (!FIXABLE(f)) {
return rb_dbl2big(f);
else {
num = dbl2ival(floor(number));
if (ndigits < 0) num = rb_int_floor(num, ndigits);
return num;
}
val = (long)f;
return LONG2FIX(val);
}
/*
@ -2003,18 +2006,27 @@ flo_ceil(int argc, VALUE *argv, VALUE num)
int ndigits = 0;
if (rb_check_arity(argc, 0, 1)) {
ndigits = NUM2INT(argv[0]);
ndigits = NUM2INT(argv[0]);
}
number = RFLOAT_VALUE(num);
if (ndigits < 0) {
return rb_int_ceil(dbl2ival(ceil(number)), ndigits);
if (number == 0.0) {
return ndigits > 0 ? DBL2NUM(number) : INT2FIX(0);
}
if (ndigits == 0) {
return dbl2ival(ceil(number));
if (ndigits > 0) {
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 = ceil(number * f) / f;
return DBL2NUM(f);
}
else {
num = dbl2ival(ceil(number));
if (ndigits < 0) num = rb_int_ceil(num, ndigits);
return num;
}
if (float_invariant_round(number, ndigits, &num)) return num;
f = pow(10, ndigits);
return DBL2NUM(ceil(number * f) / f);
}
static int
@ -2252,27 +2264,33 @@ flo_round(int argc, VALUE *argv, VALUE num)
ndigits = NUM2INT(nd);
}
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) {
return rb_int_round(flo_to_i(num), ndigits, mode);
}
number = RFLOAT_VALUE(num);
if (ndigits == 0) {
x = ROUND_CALL(mode, round, (number, 1.0));
return dbl2ival(x);
}
if (float_invariant_round(number, ndigits, &num)) return num;
f = pow(10, ndigits);
x = ROUND_CALL(mode, round, (number, f));
return DBL2NUM(x / f);
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);
x = ROUND_CALL(mode, round, (number, f));
return DBL2NUM(x / f);
}
return num;
}
static int
float_invariant_round(double number, int ndigits, VALUE *num)
float_round_overflow(int ndigits, int binexp)
{
enum {float_dig = DBL_DIG+2};
int binexp;
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
@ -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
If ndigits + ceil(binexp/(3 or 4)) < 0 the result is 0
*/
if (isinf(number) || isnan(number) ||
(ndigits >= float_dig - (binexp > 0 ? binexp / 4 : binexp / 3 - 1))) {
if (ndigits >= float_dig - (binexp > 0 ? binexp / 4 : binexp / 3 - 1)) {
return TRUE;
}
return FALSE;
}
static int
float_round_underflow(int ndigits, int binexp)
{
if (ndigits < - (binexp > 0 ? binexp / 3 + 1 : binexp / 4)) {
*num = DBL2NUM(0);
return TRUE;
}
return FALSE;

Просмотреть файл

@ -457,6 +457,8 @@ class TestFloat < Test::Unit::TestCase
end
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.110, 1.111.floor(2))
assert_equal(11110, 11119.9.floor(-1))
@ -484,6 +486,8 @@ class TestFloat < Test::Unit::TestCase
end
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.120, 1.111.ceil(2))
assert_equal(11120, 11111.1.ceil(-1))