enumerator.c: fix arith_seq_first for Infinity

* enumerator.c (arith_seq_first): fix for Float::INFINITY.

* test/ruby/test_arithmetic_sequence.rb: add tests.

* numeric.c (ruby_float_step_size): export for internal use.

* internal.h: add prototype declaration of ruby_float_step_size.

[ruby-core:90937][Bug #15518]

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@66947 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
mrkn 2019-01-30 06:05:57 +00:00
Родитель e1e3d642bf
Коммит 6f6cf042d2
4 изменённых файлов: 137 добавлений и 12 удалений

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

@ -2809,27 +2809,132 @@ rb_arithmetic_sequence_extract(VALUE obj, rb_arithmetic_sequence_components_t *c
static VALUE
arith_seq_first(int argc, VALUE *argv, VALUE self)
{
VALUE b, e, s, len_1;
VALUE b, e, s, ary;
long n;
int x;
rb_check_arity(argc, 0, 1);
b = arith_seq_begin(self);
e = arith_seq_end(self);
s = arith_seq_step(self);
if (!NIL_P(e)) {
len_1 = rb_int_idiv(rb_int_minus(e, b), s);
if (rb_num_negative_int_p(len_1)) {
if (argc == 0) {
if (argc == 0) {
if (!NIL_P(e)) {
VALUE zero = INT2FIX(0);
int r = rb_cmpint(rb_num_coerce_cmp(s, zero, idCmp), s, zero);
if (r > 0 && RTEST(rb_funcall(b, '>', 1, e))) {
return Qnil;
}
if (r < 0 && RTEST(rb_funcall(b, '<', 1, e))) {
return Qnil;
}
return rb_ary_new_capa(0);
}
}
if (argc == 0) {
return b;
}
/* TODO: optimization */
// TODO: the following code should be extracted as arith_seq_take
n = NUM2LONG(argv[0]);
if (n < 0) {
rb_raise(rb_eArgError, "attempt to take negative size");
}
if (n == 0) {
return rb_ary_new_capa(0);
}
x = arith_seq_exclude_end_p(self);
if (FIXNUM_P(b) && NIL_P(e) && FIXNUM_P(s)) {
long i = FIX2LONG(b), unit = FIX2LONG(s);
ary = rb_ary_new_capa(n);
while (n > 0 && FIXABLE(i)) {
rb_ary_push(ary, LONG2FIX(i));
i += unit; // FIXABLE + FIXABLE never overflow;
--n;
}
if (n > 0) {
b = LONG2NUM(i);
while (n > 0) {
rb_ary_push(ary, b);
b = rb_big_plus(b, s);
--n;
}
}
return ary;
}
else if (FIXNUM_P(b) && FIXNUM_P(e) && FIXNUM_P(s)) {
long i = FIX2LONG(b);
long end = FIX2LONG(e);
long unit = FIX2LONG(s);
long len;
if (unit >= 0) {
if (!x) end += 1;
len = end - i;
if (len < 0) len = 0;
ary = rb_ary_new_capa((n < len) ? n : len);
while (n > 0 && i < end) {
rb_ary_push(ary, LONG2FIX(i));
if (i + unit < i) break;
i += unit;
--n;
}
}
else {
if (!x) end -= 1;
len = i - end;
if (len < 0) len = 0;
ary = rb_ary_new_capa((n < len) ? n : len);
while (n > 0 && i > end) {
rb_ary_push(ary, LONG2FIX(i));
if (i + unit > i) break;
i += unit;
--n;
}
}
return ary;
}
else if (RB_FLOAT_TYPE_P(b) || RB_FLOAT_TYPE_P(e) || RB_FLOAT_TYPE_P(s)) {
/* generate values like ruby_float_step */
double unit = NUM2DBL(s);
double beg = NUM2DBL(b);
double end = NIL_P(e) ? (unit < 0 ? -1 : 1)*HUGE_VAL : NUM2DBL(e);
double len = ruby_float_step_size(beg, end, unit, x);
long i;
if (n > len)
n = (long)len;
if (isinf(unit)) {
if (len > 0) {
ary = rb_ary_new_capa(1);
rb_ary_push(ary, DBL2NUM(beg));
}
else {
ary = rb_ary_new_capa(0);
}
}
else if (unit == 0) {
VALUE val = DBL2NUM(beg);
ary = rb_ary_new_capa(n);
for (i = 0; i < len; ++i) {
rb_ary_push(ary, val);
}
}
else {
ary = rb_ary_new_capa(n);
for (i = 0; i < n; ++i) {
double d = i*unit+beg;
if (unit >= 0 ? end < d : d < end) d = end;
rb_ary_push(ary, DBL2NUM(d));
}
}
return ary;
}
return rb_call_super(argc, argv);
}

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

@ -1639,6 +1639,7 @@ enum ruby_num_rounding_mode {
int rb_num_to_uint(VALUE val, unsigned int *ret);
VALUE ruby_num_interval_step_size(VALUE from, VALUE to, VALUE step, int excl);
double ruby_float_step_size(double beg, double end, double unit, int excl);
int ruby_float_step(VALUE from, VALUE to, VALUE step, int excl, int allow_endless);
double ruby_float_mod(double x, double y);
int rb_num_negative_p(VALUE);

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

@ -2481,7 +2481,7 @@ num_truncate(int argc, VALUE *argv, VALUE num)
return flo_truncate(argc, argv, rb_Float(num));
}
static double
double
ruby_float_step_size(double beg, double end, double unit, int excl)
{
const double epsilon = DBL_EPSILON;

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

@ -141,12 +141,31 @@ class TestArithmeticSequence < Test::Unit::TestCase
assert_equal([], seq.first(1))
assert_equal([], seq.first(3))
seq = 1.step(10, by: 0)
assert_equal(1, seq.first)
assert_equal([1], seq.first(1))
assert_equal([1, 1, 1], seq.first(3))
seq = 10.0.step(-1.0, by: -2.0)
assert_equal(10.0, seq.first)
assert_equal([10.0], seq.first(1))
assert_equal([10.0, 8.0, 6.0], seq.first(3))
end
def test_first_bug15518
bug15518 = '[Bug #15518]'
seq = (1 .. 10.0).step(1)
five_float_classes = Array.new(5) { Float }
assert_equal(five_float_classes, seq.first(5).map(&:class), bug15518)
assert_equal([1.0, 2.0, 3.0, 4.0, 5.0], seq.first(5), bug15518)
seq = (1 .. Float::INFINITY).step(1)
assert_equal(five_float_classes, seq.first(5).map(&:class), bug15518)
assert_equal([1.0, 2.0, 3.0, 4.0, 5.0], seq.first(5), bug15518)
seq = (1 .. Float::INFINITY).step(1r)
assert_equal(five_float_classes, seq.first(5).map(&:class), bug15518)
assert_equal([1.0, 2.0, 3.0, 4.0, 5.0], seq.first(5), bug15518)
end
def test_last
seq = 1.step(10)
assert_equal(10, seq.last)