mruby-numeric-ext: add Integer#bit_length

Implement Integer#bit_length in mrbgems/mruby-numeric-ext.
- Fixnum: zero returns 0; negatives follow ~self rule; count bits by shifts.
- Bigint (MRB_USE_BIGINT): handle sign; negatives via mrb_bint_rev, then bit
  length via length of mrb_bint_to_s(..., 2).
- Add tests in mrbgems/mruby-numeric-ext/test/numeric.rb.
- Update README with examples.

Co-authored-by: Codex CLI <codex@openai.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2025-08-13 13:34:17 +09:00
parent 6e84066a42
commit e60acfc6ff
3 changed files with 62 additions and 0 deletions
+2
View File
@@ -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
@@ -1,6 +1,7 @@
#include <mruby.h>
#include <mruby/numeric.h>
#include <mruby/array.h>
#include <mruby/string.h>
#include <mruby/internal.h>
#include <mruby/presym.h>
@@ -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));
+24
View File
@@ -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