diff --git a/mrbgems/mruby-numeric-ext/README.md b/mrbgems/mruby-numeric-ext/README.md index b3bbdcdb5..c5fcc2279 100644 --- a/mrbgems/mruby-numeric-ext/README.md +++ b/mrbgems/mruby-numeric-ext/README.md @@ -21,6 +21,7 @@ This gem adds the following methods: - `allbits?(mask)`: Returns `true` if all bits of `self & mask` are 1. - `anybits?(mask)`: Returns `true` if any bits of `self & mask` are 1. - `nobits?(mask)`: Returns `true` if no bits of `self & mask` are 1. +- `bit_length`: Returns the number of bits of the absolute value of `self` in binary. `0.bit_length #=> 0`; `(-1).bit_length #=> 0`; `2.bit_length #=> 2`. - `ceildiv(other)`: Returns the result of `self` divided by `other`, rounded up to the nearest integer. - `integer?`: Returns `true` (overrides `Numeric#integer?`). - `remainder(numeric)`: Returns the remainder of `self` divided by `numeric`. Equivalent to `x - y * (x / y).truncate`. @@ -69,6 +70,7 @@ p 12345.digits # => [5, 4, 3, 2, 1] p 12345.digits(16) # => [9, 3, 0, 3] p 42.size # => (depends on machine, e.g., 4 or 8) +p 5.bit_length # => 3 p 5.odd? # => true p 4.even? # => true p Integer.sqrt(16) # => 4 diff --git a/mrbgems/mruby-numeric-ext/src/numeric_ext.c b/mrbgems/mruby-numeric-ext/src/numeric_ext.c index 2366414c6..e607eb89a 100644 --- a/mrbgems/mruby-numeric-ext/src/numeric_ext.c +++ b/mrbgems/mruby-numeric-ext/src/numeric_ext.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -8,6 +9,40 @@ static mrb_value flo_remainder(mrb_state *mrb, mrb_value self); #endif +/* + * call-seq: + * int.bit_length -> integer + * + * Returns the number of bits of the absolute value of self in binary representation. + * For zero, returns 0. For negative integers, behaves as (~self).bit_length + * (e.g., (-1).bit_length => 0, (-2).bit_length => 1). + */ +static mrb_value +int_bit_length(mrb_state *mrb, mrb_value self) +{ +#ifdef MRB_USE_BIGINT + if (mrb_bigint_p(self)) { + mrb_int sign = mrb_bint_sign(mrb, self); + if (sign == 0) return mrb_fixnum_value(0); + mrb_value v = self; + if (sign < 0) v = mrb_bint_rev(mrb, self); /* ~self = -self-1 */ + mrb_value s = mrb_bint_to_s(mrb, v, 2); + return mrb_int_value(mrb, (mrb_int)RSTRING_LEN(s)); + } +#endif + mrb_int x = mrb_integer(self); + if (x == 0) return mrb_fixnum_value(0); + + /* for negative fixnums, use ~x */ + mrb_uint ux = (mrb_uint)(x < 0 ? ~x : x); + mrb_int bits = 0; + while (ux) { + bits++; + ux >>= 1; + } + return mrb_int_value(mrb, bits); +} + /* * call-seq: * num.remainder(numeric) -> real @@ -435,6 +470,7 @@ mrb_mruby_numeric_ext_gem_init(mrb_state* mrb) mrb_define_method_id(mrb, ic, MRB_SYM(pow), int_powm, MRB_ARGS_ARG(1,1)); mrb_define_method_id(mrb, ic, MRB_SYM(digits), int_digits, MRB_ARGS_OPT(1)); mrb_define_method_id(mrb, ic, MRB_SYM(size), int_size, MRB_ARGS_NONE()); + mrb_define_method_id(mrb, ic, MRB_SYM(bit_length), int_bit_length, MRB_ARGS_NONE()); mrb_define_method_id(mrb, ic, MRB_SYM_Q(odd), int_odd, MRB_ARGS_NONE()); mrb_define_method_id(mrb, ic, MRB_SYM_Q(even), int_even, MRB_ARGS_NONE()); mrb_define_method_id(mrb, ic, MRB_SYM(gcd), int_gcd, MRB_ARGS_REQ(1)); diff --git a/mrbgems/mruby-numeric-ext/test/numeric.rb b/mrbgems/mruby-numeric-ext/test/numeric.rb index 4578afd26..08eb4e9d0 100644 --- a/mrbgems/mruby-numeric-ext/test/numeric.rb +++ b/mrbgems/mruby-numeric-ext/test/numeric.rb @@ -111,3 +111,27 @@ assert('Integer.sqrt') do assert_equal(10, Integer.sqrt(100)) assert_equal(85, Integer.sqrt(7244)) end + +assert('Integer#bit_length') do + # zero + assert_equal 0, 0.bit_length + + # positives + assert_equal 1, 1.bit_length + assert_equal 2, 2.bit_length + assert_equal 2, 3.bit_length + assert_equal 3, 4.bit_length + assert_equal 3, 5.bit_length + + # negatives (use ~n semantics) + assert_equal 0, (-1).bit_length + assert_equal 1, (-2).bit_length + assert_equal 2, (-3).bit_length + assert_equal 2, (-4).bit_length + assert_equal 3, (-5).bit_length + + # bigint cases may be enabled depending on config + # assert_equal 100, (2**100 - 1).bit_length + # assert_equal 101, (2**100).bit_length + # assert_equal 0, (-1).bit_length +end