numeric.c: Extend Integer#[] to support range arguments

````
0b01001101[2, 4]  #=> 0b0011
0b01001100[2..5]  #=> 0b0011
0b01001100[2...6] #=> 0b0011
    ^^^^
````

[Feature #8842]
This commit is contained in:
Yusuke Endoh 2019-04-28 23:24:09 +09:00
Родитель cb55024613
Коммит 6bedbf4625
2 изменённых файлов: 150 добавлений и 23 удалений

148
numeric.c
Просмотреть файл

@ -4630,24 +4630,6 @@ rb_int_rshift(VALUE x, VALUE y)
return Qnil;
}
/*
* Document-method: Integer#[]
* call-seq:
* int[n] -> 0, 1
*
* Bit Reference---Returns the <code>n</code>th bit in the
* binary representation of +int+, where <code>int[0]</code>
* is the least significant bit.
*
* a = 0b11001100101010
* 30.downto(0) {|n| print a[n] }
* #=> 0000000000000000011001100101010
*
* a = 9**15
* 50.downto(0) {|n| print a[n] }
* #=> 000101110110100000111000011110010100111100010111001
*/
static VALUE
fix_aref(VALUE fix, VALUE idx)
{
@ -4675,18 +4657,138 @@ fix_aref(VALUE fix, VALUE idx)
return INT2FIX(0);
}
static VALUE
int_aref(VALUE num, VALUE idx)
/* copied from "r_less" in range.c */
/* compares _a_ and _b_ and returns:
* < 0: a < b
* = 0: a = b
* > 0: a > b or non-comparable
*/
static int
compare_indexes(VALUE a, VALUE b)
{
VALUE r = rb_funcall(a, id_cmp, 1, b);
if (NIL_P(r))
return INT_MAX;
return rb_cmpint(r, a, b);
}
static VALUE
generate_mask(VALUE len) {
return rb_int_minus(rb_int_lshift(INT2FIX(1), len), INT2FIX(1));
}
static VALUE
int_aref1(VALUE num, VALUE arg)
{
VALUE orig_num = num, beg, end;
int excl;
if (rb_range_values(arg, &beg, &end, &excl)) {
if (NIL_P(beg)) {
/* beginless range */
if (!RTEST(num_negative_p(end))) {
if (!excl) end = rb_int_plus(end, INT2FIX(1));
VALUE mask = generate_mask(end);
if (RTEST(num_zero_p(rb_int_and(num, mask)))) {
return INT2FIX(0);
}
else {
rb_raise(rb_eArgError, "The beginless range for Integer#[] results in infinity");
}
}
else {
return INT2FIX(0);
}
}
num = rb_int_rshift(num, beg);
int cmp = compare_indexes(beg, end);
if (!NIL_P(end) && cmp < 0) {
VALUE len = rb_int_minus(end, beg);
if (!excl) len = rb_int_plus(len, INT2FIX(1));
VALUE mask = generate_mask(len);
num = rb_int_and(num, mask);
}
else if (cmp == 0) {
if (excl) return INT2FIX(0);
num = orig_num;
arg = beg;
goto one_bit;
}
return num;
}
one_bit:
if (FIXNUM_P(num)) {
return fix_aref(num, idx);
return fix_aref(num, arg);
}
else if (RB_TYPE_P(num, T_BIGNUM)) {
return rb_big_aref(num, idx);
return rb_big_aref(num, arg);
}
return Qnil;
}
static VALUE
int_aref2(VALUE num, VALUE beg, VALUE len)
{
num = rb_int_rshift(num, beg);
VALUE mask = generate_mask(len);
num = rb_int_and(num, mask);
return num;
}
/*
* Document-method: Integer#[]
* call-seq:
* int[n] -> 0, 1
* int[n, m] -> num
* int[range] -> num
*
* Bit Reference---Returns the <code>n</code>th bit in the
* binary representation of +int+, where <code>int[0]</code>
* is the least significant bit.
*
* a = 0b11001100101010
* 30.downto(0) {|n| print a[n] }
* #=> 0000000000000000011001100101010
*
* a = 9**15
* 50.downto(0) {|n| print a[n] }
* #=> 000101110110100000111000011110010100111100010111001
*
* In principle, <code>n[i]</code> is equivalent to <code>(n >> i) & 1</code>.
* Thus, any negative index always returns zero:
*
* p 255[-1] #=> 0
*
* Range operations <code>n[i, len]</code> and <code>n[i..j]</code>
* are naturally extended.
*
* * <code>n[i, len]</code> equals to <code>(n >> i) & ((1 << len) - 1)</code>.
* * <code>n[i..j]</code> equals to <code>(n >> i) & ((1 << (j - i + 1)) - 1)</code>.
* * <code>n[i...j]</code> equals to <code>(n >> i) & ((1 << (j - i)) - 1)</code>.
* * <code>n[i..]</code> equals to <code>(n >> i)</code>.
* * <code>n[..j]</code> is zero if <code>n & ((1 << (j + 1)) - 1)</code> is zero. Otherwise, raises an ArgumentError.
* * <code>n[...j]</code> is zero if <code>n & ((1 << j) - 1)</code> is zero. Otherwise, raises an ArgumentError.
*
* Note that range operation may exhaust memory.
* For example, <code>-1[0, 1000000000000]</code> will raise NoMemoryError.
*/
static VALUE
int_aref(int const argc, VALUE * const argv, VALUE const num)
{
rb_check_arity(argc, 1, 2);
if (argc == 2) {
return int_aref2(num, argv[0], argv[1]);
}
return int_aref1(num, argv[0]);
return Qnil;
}
/*
* Document-method: Integer#to_f
* call-seq:
@ -5555,7 +5657,7 @@ Init_Numeric(void)
rb_define_method(rb_cInteger, "&", rb_int_and, 1);
rb_define_method(rb_cInteger, "|", int_or, 1);
rb_define_method(rb_cInteger, "^", int_xor, 1);
rb_define_method(rb_cInteger, "[]", int_aref, 1);
rb_define_method(rb_cInteger, "[]", int_aref, -1);
rb_define_method(rb_cInteger, "<<", rb_int_lshift, 1);
rb_define_method(rb_cInteger, ">>", rb_int_rshift, 1);

Просмотреть файл

@ -23,6 +23,31 @@ class TestInteger < Test::Unit::TestCase
(-64..64).each do |idx|
assert_equal((n >> idx) & 1, n[idx])
end
[*-66..-62, *-34..-30, *-5..5, *30..34, *62..66].each do |idx|
(0..100).each do |len|
assert_equal((n >> idx) & ((1 << len) - 1), n[idx, len], "#{ n }[#{ idx }, #{ len }]")
end
(0..100).each do |len|
assert_equal((n >> idx) & ((1 << (len + 1)) - 1), n[idx..idx+len], "#{ n }[#{ idx }..#{ idx+len }]")
assert_equal((n >> idx) & ((1 << len) - 1), n[idx...idx+len], "#{ n }[#{ idx }...#{ idx+len }]")
end
# endless
assert_equal((n >> idx), n[idx..], "#{ n }[#{ idx }..]")
assert_equal((n >> idx), n[idx...], "#{ n }[#{ idx }...#]")
# beginless
if idx >= 0 && n & ((1 << (idx + 1)) - 1) != 0
assert_raise(ArgumentError, "#{ n }[..#{ idx }]") { n[..idx] }
else
assert_equal(0, n[..idx], "#{ n }[..#{ idx }]")
end
if idx >= 0 && n & ((1 << idx) - 1) != 0
assert_raise(ArgumentError, "#{ n }[...#{ idx }]") { n[...idx] }
else
assert_equal(0, n[...idx], "#{ n }[...#{ idx }]")
end
end
end
# assert_equal(1, (1 << 0x40000000)[0x40000000], "[ruby-dev:31271]")