[前][次][番号順一覧][スレッド一覧]

ruby-changes:54730

From: mrkn <ko1@a...>
Date: Wed, 30 Jan 2019 15:06:05 +0900 (JST)
Subject: [ruby-changes:54730] mrkn:r66947 (trunk): enumerator.c: fix arith_seq_first for Infinity

mrkn	2019-01-30 15:05:57 +0900 (Wed, 30 Jan 2019)

  New Revision: 66947

  https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=66947

  Log:
    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]

  Modified files:
    trunk/enumerator.c
    trunk/internal.h
    trunk/numeric.c
    trunk/test/ruby/test_arithmetic_sequence.rb
Index: numeric.c
===================================================================
--- numeric.c	(revision 66946)
+++ numeric.c	(revision 66947)
@@ -2481,7 +2481,7 @@ num_truncate(int argc, VALUE *argv, VALU https://github.com/ruby/ruby/blob/trunk/numeric.c#L2481
     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;
Index: test/ruby/test_arithmetic_sequence.rb
===================================================================
--- test/ruby/test_arithmetic_sequence.rb	(revision 66946)
+++ test/ruby/test_arithmetic_sequence.rb	(revision 66947)
@@ -141,12 +141,31 @@ class TestArithmeticSequence < Test::Uni https://github.com/ruby/ruby/blob/trunk/test/ruby/test_arithmetic_sequence.rb#L141
     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)
Index: enumerator.c
===================================================================
--- enumerator.c	(revision 66946)
+++ enumerator.c	(revision 66947)
@@ -2809,27 +2809,132 @@ rb_arithmetic_sequence_extract(VALUE obj https://github.com/ruby/ruby/blob/trunk/enumerator.c#L2809
 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);
         }
+        return b;
     }
 
-    if (argc == 0) {
-        return b;
+    // 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);
 
-    /* TODO: optimization */
+    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);
 }
Index: internal.h
===================================================================
--- internal.h	(revision 66946)
+++ internal.h	(revision 66947)
@@ -1639,6 +1639,7 @@ enum ruby_num_rounding_mode { https://github.com/ruby/ruby/blob/trunk/internal.h#L1639
 
 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);

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]