Issue #5271 has been updated by Marc-Andre Lafortune.

Status changed from Closed to Open

Opening backport request.
----------------------------------------
Backport #5271: Integer#round should never return a Float
http://redmine.ruby-lang.org/issues/5271

Author: Marc-Andre Lafortune
Status: Open
Priority: Normal
Assignee: 
Category: core
Target version: 1.9.3


Integer#round sometimes returns... a float!

    42.round(-1e9) # => 0.0

There's a check for out of range arguments, but it's not really needed and is machine dependent.

The following patch fixes the issue by optimizing for most cases and double checking for the rare extremely unlikely case where 10**ndigits does not fit in a bignum but `num` does and has almost `ndigits` digits.

Committed to trunk but would be nice to backport for Ruby 1.9.3.



diff --git a/numeric.c b/numeric.c
index 201dfab..a767fa5 100644
--- a/numeric.c
+++ b/numeric.c
@@ -3320,6 +3320,7 @@ int_round(int argc, VALUE* argv, VALUE num)
 {
     VALUE n, f, h, r;
     int ndigits;
+    long bytes;
     ID op;
 
     if (argc == 0) return num;
@@ -3331,11 +3332,15 @@ int_round(int argc, VALUE* argv, VALUE num)
     if (ndigits == 0) {
        return num;
     }
-    ndigits = -ndigits;
-    if (ndigits < 0) {
-       rb_raise(rb_eArgError, "ndigits out of range");
+
+    /* If 10**N / 2 > num, then return 0 */
+    /* We have log_256(10) > 0.415241 and log_256(1/2) = -0.125, so */
+    bytes = FIXNUM_P(num) ? sizeof(long) : rb_funcall(num, rb_intern("size"), 0);
+    if (-0.415241 * ndigits - 0.125 > bytes ) {
+       return INT2FIX(0);
     }
-    f = int_pow(10, ndigits);
+
+    f = int_pow(10, -ndigits);
     if (FIXNUM_P(num) && FIXNUM_P(f)) {
        SIGNED_VALUE x = FIX2LONG(num), y = FIX2LONG(f);
        int neg = x < 0;
@@ -3344,6 +3349,10 @@ int_round(int argc, VALUE* argv, VALUE num)
        if (neg) x = -x;
        return LONG2NUM(x);
     }
+    if (TYPE(f) == T_FLOAT) {
+       /* then int_pow overflow */
+       return INT2FIX(0);
+    }
     h = rb_funcall(f, '/', 1, INT2FIX(2));
     r = rb_funcall(num, '%', 1, f);
     n = rb_funcall(num, '-', 1, r);



-- 
http://redmine.ruby-lang.org