aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeremy Evans <code@jeremyevans.net>2021-06-17 11:27:53 -0700
committerJeremy Evans <code@jeremyevans.net>2021-07-29 15:19:12 -0700
commit9931e2f5091e95dd947de3b3a00167ae2fd5194a (patch)
treef8b70b54c37bc3ee0b03b3a057c0bcbfd9f89a5a
parent64ac984129a7a4645efe5ac57c168ef880b479b2 (diff)
downloadruby-9931e2f5091e95dd947de3b3a00167ae2fd5194a.tar.gz
Improve performance of Integer#digits
This speeds up performance by multiple orders of magnitude for large integers. Fixes [Bug #14391] Co-authored-by: tompng (tomoya ishida) <tomoyapenguin@gmail.com>
-rw-r--r--numeric.c33
-rw-r--r--test/ruby/test_integer.rb2
2 files changed, 29 insertions, 6 deletions
diff --git a/numeric.c b/numeric.c
index 5564a6eceb..c60853f355 100644
--- a/numeric.c
+++ b/numeric.c
@@ -4805,7 +4805,7 @@ rb_fix_digits(VALUE fix, long base)
static VALUE
rb_int_digits_bigbase(VALUE num, VALUE base)
{
- VALUE digits;
+ VALUE digits, bases;
assert(!rb_num_negative_p(num));
@@ -4823,11 +4823,32 @@ rb_int_digits_bigbase(VALUE num, VALUE base)
if (FIXNUM_P(num))
return rb_ary_new_from_args(1, num);
- digits = rb_ary_new();
- while (!FIXNUM_P(num) || FIX2LONG(num) > 0) {
- VALUE qr = rb_int_divmod(num, base);
- rb_ary_push(digits, RARRAY_AREF(qr, 1));
- num = RARRAY_AREF(qr, 0);
+ if (int_lt(rb_int_div(rb_int_bit_length(num), rb_int_bit_length(base)), INT2FIX(50))) {
+ digits = rb_ary_new();
+ while (!FIXNUM_P(num) || FIX2LONG(num) > 0) {
+ VALUE qr = rb_int_divmod(num, base);
+ rb_ary_push(digits, RARRAY_AREF(qr, 1));
+ num = RARRAY_AREF(qr, 0);
+ }
+ return digits;
+ }
+
+ bases = rb_ary_new();
+ for (VALUE b = base; int_lt(b, num) == Qtrue; b = rb_int_mul(b, b)) {
+ rb_ary_push(bases, b);
+ }
+ digits = rb_ary_new_from_args(1, num);
+ while (RARRAY_LEN(bases)) {
+ VALUE b = rb_ary_pop(bases);
+ long i, last_idx = RARRAY_LEN(digits) - 1;
+ for(i = last_idx; i >= 0; i--) {
+ VALUE n = RARRAY_AREF(digits, i);
+ VALUE divmod = rb_int_divmod(n, b);
+ VALUE div = RARRAY_AREF(divmod, 0);
+ VALUE mod = RARRAY_AREF(divmod, 1);
+ if (i != last_idx || div != INT2FIX(0)) rb_ary_store(digits, 2 * i + 1, div);
+ rb_ary_store(digits, 2 * i, mod);
+ }
}
return digits;
diff --git a/test/ruby/test_integer.rb b/test/ruby/test_integer.rb
index 1cd256a1cf..9354514df0 100644
--- a/test/ruby/test_integer.rb
+++ b/test/ruby/test_integer.rb
@@ -576,6 +576,8 @@ class TestInteger < Test::Unit::TestCase
assert_equal([0, 9, 8, 7, 6, 5, 4, 3, 2, 1], 1234567890.digits)
assert_equal([90, 78, 56, 34, 12], 1234567890.digits(100))
assert_equal([10, 5, 6, 8, 0, 10, 8, 6, 1], 1234567890.digits(13))
+ assert_equal((2 ** 1024).to_s(7).chars.map(&:to_i).reverse, (2 ** 1024).digits(7))
+ assert_equal([0] * 100 + [1], (2 ** (128 * 100)).digits(2 ** 128))
end
def test_digits_for_negative_numbers