From 6f6cf042d2e348e73504258fe36f4bd27694a640 Mon Sep 17 00:00:00 2001 From: mrkn Date: Wed, 30 Jan 2019 06:05:57 +0000 Subject: [PATCH] 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 --- enumerator.c | 127 +++++++++++++++++++++++--- internal.h | 1 + numeric.c | 2 +- test/ruby/test_arithmetic_sequence.rb | 19 ++++ 4 files changed, 137 insertions(+), 12 deletions(-) diff --git a/enumerator.c b/enumerator.c index 609fc71015..6f50ce97a2 100644 --- a/enumerator.c +++ b/enumerator.c @@ -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); } diff --git a/internal.h b/internal.h index 36e03ff9df..77f9a3202f 100644 --- a/internal.h +++ b/internal.h @@ -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); diff --git a/numeric.c b/numeric.c index 1221eafa9e..a1090ff85d 100644 --- a/numeric.c +++ b/numeric.c @@ -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; diff --git a/test/ruby/test_arithmetic_sequence.rb b/test/ruby/test_arithmetic_sequence.rb index b4c93d46c7..9bff9a21c4 100644 --- a/test/ruby/test_arithmetic_sequence.rb +++ b/test/ruby/test_arithmetic_sequence.rb @@ -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)