

Rubyで指定したビットの修正を行う方法を探しています。  
公式リファレンスによるとRuby2.7以降には、`Integer#[]` という非常に便利なメソッドがあり、

[code]

    i[nth, len] # (n >> i) & ((1 << len) - 1) と同じ 
    i[range]
    
[/code]

とすると、ビットを取り出すことができるそうです。

<https://docs.ruby-lang.org/ja/latest/method/Integer/i/=5b=5d.html>

一方で

> self[nth]=bit (つまりビットの修正) がないのは、Numeric 関連クラスが immutable であるためです。

という記載もあり、ビットを修正することができません。（Integerは変更できないので、新たにIntegerオブジェクトを作成しなければなりません）ここに記載されているビットの修正

[code]

    self[nth, len] = n
    
[/code]

に相当する操作はRubyでどう書けばよいでしょうか？

ウィキペディアの[マスク（情報工学）](https://ja.wikipedia.org/wiki/%E3%83%9E%E3%82%B9%E3%82%AF_\(%E6%83%85%E5%A0%B1%E5%B7%A5%E5%AD%A6\))の記事を参考に、次のように書いてみました。

[code]

    class Integer
      def []=(start, width, value)
        if value.bit_length > width
          # FIXME: valueが負の場合は少しエラーがおかしい…
          raise "Value #{value} is larger than #{(1 << width) - 1}"
        end
    
        all = ((1 << self.bit_length) - 1)
        mask = all ^ (((1 << width) - 1) << start)
        masked_value = self & mask
        new_value = masked_value | (value << start)
        p new_value
      end
    end
    
[/code]

私が思いついたやり方はこれですが、ほかにもっと良い方法があるかも知れません。ご存知の方は教えてください。また上記の実装方法の不備みたいなものがありましたら指摘して頂けると助かります。よろしくおねがいします。


CC BY-SA 4.0
2021-07-16T15:18:10.663
78236
2021-07-22T22:40:07.800
2021-07-16T15:29:51.430
32772
32772
question
ruby
Rubyでビットの修正を行う方法


とあるslackによると

[code]

    class Integer
      def bitset(start, width, value)
        mask = ~(~0 << width)
        (self & ~(mask << start)) | ((value & mask) << start)
      end
    end
    
[/code]


CC BY-SA 4.0
2021-07-22T15:42:44.047
78366
2021-07-22T15:42:44.047
32772
78236
answer


[]= が bf_set() で Array でも指定できます 0.bf_set(3,5,[1,0,1,1,0]) #=> 176  
それだけだと寂しいので、適当に書き足しました。  
高速なアルゴリズムより単純でコンパクトさを重視して実装しました。(つまり手抜き)

[code]

    # bf_???  Bit Field ...
    # Ruby 互換性メモ
    #   一行メソッド定義は Ruby 3.0.0 以降
    #   Integer#bit_length は Runy 2.1.0 以降
    class Integer
      def bf_set( start, width, value ) # bf_set(start, width, value) -> Integer; bf_set(start, width, [array]) -> Integer
        if value.is_a?( Array )
    #     raise if width > value.size   # width と value.size は一致を期待しない (下位bitを優先)
          value = value.inject(0){|r,w| r << 1 | ( (w == 0 || w == ?0 || w == false || w == nil)? 0 : 1 ) }
        end
        mask = (1 << width) - 1 << start
        (mask & self ^ self) | (mask & value << start)
      end
      def bf_and( start, width, value )
        mask = (1 << width) - 1 << start
        (mask & self ^ self) | (mask & value << start) & self
      end
      def bf_add( start, width, value )
        mask = (1 << width) - 1 << start
        (mask & self ^ self) | mask & ( self + (value << start) )
      end
      def bf_inc( start, width ) = self.bf_add( start, width, 1 )
      def bf_dec( start, width ) = self.bf_add( start, width, -1 )
      def bf_get( start, width ) = (1 << width) - 1 & self >> start # self[ start, width ]
      def bf_or(  start, width, value ) = (((1 << width) - 1) & value) << start | self
      def bf_xor( start, width, value ) = (((1 << width) - 1) & value) << start ^ self
      def bf_clear( start, width ) = ((1 << width) - 1) << start & self ^ self
      def bf_full(  start, width ) = ((1 << width) - 1) << start | self
      def bf_not(   start, width ) = ((1 << width) - 1) << start ^ self
      def bf_rotate( start, width, cnt )
        mask = (1 << width) - 1 << start
        rotate = cnt % width
        wrk = self & mask
        val = self ^ wrk
        val |= mask & (wrk << rotate | wrk >> width - rotate)
      end
      def bf_sign( start, width )  # sign -> -1 | 0 | 1
        case
        when self & 1 << start + width - 1 != 0    then -1
        when self & (1 << width) - 1 << start == 0 then 0
        else 1
        end
      end
      def bf_reverse( start, width = nil )  # bf_reverse(start, width) -> Integer; bf_reverse(Range) -> Integer
        if start.is_a?( Range )     # 12..4 だと start.min, start.max 共に nil だが .first, .last は有効
          width = start.last
          start = start.first
          start, width = width, start  if start > width
          width -= start
        end
        mask = 1 << start
        wrk = ((1 << width) - 1 << start) & self
        val = wrk ^ self
        rv = 0
        width.times{
          rv = rv << 1 | wrk & mask
          wrk >>= 1
        }
        val | rv
      end
      def bf_count( start, width )  # 1の数
        val = ( ((1 << width) - 1 << start) & self ) >> start  # ループ内の処理を軽くする(>> start)
        count = 0
        while val != 0
          val &= val - 1
          count += 1
        end
        count
      end
      def bf_max( start, width )    # 1が連続する最大長さ。0101110110 -> 3, 0101010101 -> 1
        val = ( ((1 << width) - 1 << start) & self ) >> start
        max = 0
        while val != 0
          val &= val >> 1
          max += 1
        end
        max
      end
      def bf_min( start, width )    # 1が連続する最小長さ。01111001110110 -> 2, 0101010101 -> 1
        val = ( ((1 << width) - 1 << start) & self ) >> start
        min = 0
        while val != 0
          min += 1
          w = val & val >> 1
          break if val != w | w << 1
          val = w
        end
        min
      end
      def bf_rbit( start, width )   # 最も下位の1の bit位置 0->0, 1...
        val = ( ((1 << width) - 1 << start) & self ) >> start
        ( val & -val ).bit_length
      end
      def bf_lbit( start, width )   # 最も上位の1の bit位置
        (( ((1 << width) - 1 << start) & self ) >> start).bit_length
      end
      def bf_unpack( width )    # bf_unpack( [width] ) -> [val] # 0x52f50170.bf_unpack([7,4,5, 5,6,5]) #=> [41,7,21,0,11,16]
        raise unless width.is_a?(Array)
        val = []
        wrk = self
        width.reverse.each{|w|
          val.unshift( (1 << w.to_i) - 1 & wrk )
          wrk >>= w.to_i
        }
        val
      end
      def bf_dot?( start, width )   # 1 が一つだけなら真 (2**n)
        val = ( ((1 << width) - 1 << start) & self ) >> start
        (val == 0)? false : val & val - 1 == 0
      end
    end
    
    class Array
      def bf_pack( value )  # [width].bt_pack( [value] ) -> Integer # width, value はリトルエンディアン
        raise unless value.is_a?(Array) && self.size == value.size
        val = 0
        start = 0
        self.reverse.each_with_index{|w,i|
          val = val.bf_set( start, w.to_i, value[ value.size-i-1 ] )
          start += w.to_i
        }
        val
      end
    end
    
[/code]


CC BY-SA 4.0
2021-07-22T22:40:07.800
78370
2021-07-22T22:40:07.800
40304
78236
answer