diff --git a/numeric.c b/numeric.c index a02dfd3..05f0514 100644 --- a/numeric.c +++ b/numeric.c @@ -3296,6 +3296,72 @@ int_round(int argc, VALUE* argv, VALUE num) /* * call-seq: + * int.each_modulo(n) -> + * + * If a block given, enumerates with iterated modulo and returns self. + * Otherwise, returns an Enumerator. + */ + +static VALUE +int_each_modulo(VALUE num, VALUE modulus) +{ + VALUE big_q, qr; + long q, r, m; + + switch (TYPE(modulus)) { + case T_BIGNUM: + if (rb_big_cmp(modulus, rb_int2big(1)) <= 0) { + goto arg_must_be_lt_1; + } + break; + + case T_FIXNUM: + if (FIX2INT(modulus) <= 1) { +arg_must_be_lt_1: + rb_raise(rb_eArgError, "argument must be larger than 1"); + } + break; + + default: + rb_raise(rb_eArgError, "argument must be an Integer (%s)", + rb_obj_classname(modulus)); + } + + RETURN_ENUMERATOR(num, 1, &modulus); + + big_q = num; + if (TYPE(big_q) == T_BIGNUM) { + while (TYPE(big_q) == T_BIGNUM) { + qr = rb_big_divmod(big_q, modulus); + rb_yield(RARRAY_PTR(qr)[1]); + big_q = RARRAY_PTR(qr)[0]; + } + } + + q = NUM2INT(big_q); + switch (TYPE(modulus)) { + case T_BIGNUM: + while (q > 0) { + qr = rb_big_divmod(rb_int2big(q), modulus); + rb_yield(RARRAY_PTR(qr)[1]); + q = NUM2INT(RARRAY_PTR(qr)[0]); + } + break; + + case T_FIXNUM: + m = FIX2INT(modulus); + while (q > 0) { + fixdivmod(q, m, &q, &r); + rb_yield(INT2FIX(r)); + } + break; + } + + return num; +} + +/* + * call-seq: * fix.zero? -> true or false * * Returns true if fix is zero. @@ -3454,6 +3520,8 @@ Init_Numeric(void) rb_define_method(rb_cInteger, "truncate", int_to_i, 0); rb_define_method(rb_cInteger, "round", int_round, -1); + rb_define_method(rb_cInteger, "each_modulo", int_each_modulo, 1); + rb_cFixnum = rb_define_class("Fixnum", rb_cInteger); rb_define_method(rb_cFixnum, "to_s", fix_to_s, -1); diff --git a/test/ruby/test_integer.rb b/test/ruby/test_integer.rb index 7f8212e..2686382 100644 --- a/test/ruby/test_integer.rb +++ b/test/ruby/test_integer.rb @@ -198,4 +198,37 @@ class TestInteger < Test::Unit::TestCase assert_equal(-1111_1111_1111_1111_1111_1111_1111_1110, (-1111_1111_1111_1111_1111_1111_1111_1111).round(-1)) assert_equal(Bignum, (-1111_1111_1111_1111_1111_1111_1111_1111).round(-1).class) end + + def test_each_modulo_returns_Enumerator_unless_block_given + assert_instance_of(Enumerator, 1111.each_modulo(10)) + assert_instance_of(Enumerator, 1111.each_modulo(2**100)) + assert_instance_of(Enumerator, (2**100).each_modulo(10)) + assert_instance_of(Enumerator, (2**100).each_modulo(2**100)) + end + + def test_each_modulo_calls_block_with_values_of_iterated_modulo + assert_equal([1, 2, 2, 1, 1], 133.each_modulo(3).to_a) + assert_equal([133], 133.each_modulo(2**100).to_a) + assert_equal([1, 2, 1], (2**200).each_modulo(2**100-1).to_a) + end + + def test_each_modulo_raise_ArgumentError_when_arg_le_1 + assert_raise(ArgumentError) { 111.each_modulo(1) } + assert_raise(ArgumentError) { 111.each_modulo(0) } + assert_raise(ArgumentError) { 111.each_modulo(-42) } + assert_raise(ArgumentError) { (2**100).each_modulo(1) } + assert_raise(ArgumentError) { (2**100).each_modulo(0) } + assert_raise(ArgumentError) { (2**100).each_modulo(-42) } + end + + def test_each_modulo_raise_ArgumentError_when_arg_not_int + assert_raise(ArgumentError) { 111.each_modulo(Object.new) } + assert_raise(ArgumentError) { 111.each_modulo(1.0) } + assert_raise(ArgumentError) { 111.each_modulo(1.quo(3)) } + assert_raise(ArgumentError) { 111.each_modulo(Complex(1, 1)) } + assert_raise(ArgumentError) { (2**100).each_modulo(Object.new) } + assert_raise(ArgumentError) { (2**100).each_modulo(1.0) } + assert_raise(ArgumentError) { (2**100).each_modulo(1.quo(3)) } + assert_raise(ArgumentError) { (2**100).each_modulo(Complex(1, 1)) } + end end