C言語の勉強をしていたら、ビット演算が出てきました。Elixirでもやってみようと思います。
ビットマスク
- ビット演算と呼ぶビット単位の操作を行う処理である
- ビットマスクをつかってできること
- 特定のビットをオン(
1
)やオフ(0
)にする - 特定のビットの状態(
0
か1
)を知る
- 特定のビットをオン(
ビット演算
- データをビット列(つまり
0
か1
が多数並んだもの)と見なして、各ビットの移動やビット単位での論理演算を行うもの。
Elixirでビット演算
Bitwiseモジュールの関数または演算子を使用して、整数に対してビット単位の演算を実行できます。
関数 | 演算子 | |
---|---|---|
bitwise AND | band/2 |
&&& |
bitwise OR | bor/2 |
||| |
bitwise SHIFT LEFT | bsl/2 |
<<< |
bitwise SHIFT RIGHT | bsr/2 |
>>> |
bitwise XOR | bxor/2 |
なし |
bitwise NOT | bnot/1 |
なし |
昔は XOR と NOT にも演算子があった気がしますが、いつの間にか無くなっています。
Bitwiseモジュールの関数・演算子は、ガード節でも使用できます。
Bitwiseモジュールを使用する前には以下おまじないを実行してください。これにより、モジュール名を明示せずに全ての関数と演算子が使用できるようになります。
import Bitwise
bitwise NOT
image credit: https://en.wikipedia.org/wiki/Negation
NOT 0111 (decimal 7)
= 1000 (decimal 8)
Elixirではこうやります。
-0b1000 = bnot(0b0111)
どうも符号が反転するようです。@kikuyuta 先生の解説によると「もとの数を n
とすると bnot(n)
は -n-1
の値を返す」そうです。
符号を無視するのであれば絶対値を取ればいいかもしれません。
0b1000 = abs(bnot(0b0111))
整数の二進法表記を確認したい場合は、Integer.to_string/2
もしくはinspect/2
が便利です。どのフラグが立っているのか視覚的に確認できて有用です。
8 |> Integer.to_string(2)
# "1000"
8 |> inspect(base: :binary)
# "0b1000"
bitwise AND
image credit: https://en.wikipedia.org/wiki/Logical_conjunction
0101 (decimal 5)
AND 0011 (decimal 3)
= 0001 (decimal 1)
Elixirではこうやります
0b0001 = 0b0101 |> band(0b0011)
0b0001 = 0b0101 &&& 0b0011
bitwise OR
image credit: https://en.wikipedia.org/wiki/Logical_disjunction
0101 (decimal 5)
OR 0011 (decimal 3)
= 0111 (decimal 7)
Elixirではこうやります
0b0111 = 0b0101 |> bor(0b0011)
0b0111 = 0b0101 ||| 0b0011
bitwise XOR
image credit: https://en.wikipedia.org/wiki/Exclusive_or
0101 (decimal 5)
XOR 0011 (decimal 3)
= 0110 (decimal 6)
Elixirではこうやります
0b0110 = 0b0101 |> bxor(0b0011)
Elixirでビットマスク
ビットマスクを作る関数を用意します。最下位ビット(LSB)の位置を基準とし、そこからの相対位置(オフセット)にあるビットをたてます。
import Bitwise
mask = fn bit_position ->
1 <<< bit_position
end
この関数を用いて各ビットを操作するビットマスクを作ります。
0b00000001 = mask.(0)
0b00000010 = mask.(1)
0b00000100 = mask.(2)
0b00001000 = mask.(3)
0b00010000 = mask.(4)
0b00100000 = mask.(5)
0b01000000 = mask.(6)
0b10000000 = mask.(7)
複数のビットを操作したい場合は、ビットマスクを組み合わせることができます。
# 最下位ビットから2番目と4番目と6番目のビットを立てる
0b00101010 = mask.(1) ||| mask.(3) ||| mask.(5)
特定のビットをオン
add_flags_by_mask = fn uint8, mask ->
uint8 ||| mask
end
# 最下位ビットから2番目と4番目と6番目のビットをオンにする
0b00101010 =
0b00000000
|> add_flags_by_mask.(mask.(1) ||| mask.(3) ||| mask.(5))
特定のビットをオフ
remove_flags_by_mask = fn uint8, mask ->
uint8 &&& bnot(mask)
end
# 下位4ビットをオフにする
0b11110000 =
0b11111111
|> remove_flags_by_mask.(mask.(0) ||| mask.(1) ||| mask.(2) ||| mask.(3))
特定のビットを反転
invert_flags_by_mask = fn uint8, mask ->
uint8 |> bxor(mask)
end
# 最下位ビットから2番目と4番目のビットを反転する
0b00000010 =
0b00001000
|> invert_flags_by_mask.(mask.(1) ||| mask.(3))
特定のビットの状態を知る
read_bit_by_mask = fn
uint8, mask when (uint8 &&& mask) == 0 -> 0
_, _ -> 1
end
# 最下位ビットから4番目のビットがオンであるか確認
1 = 0b00001000 |> read_bit_by_mask.(mask.(3))
0 = 0b00000000 |> read_bit_by_mask.(mask.(3))
この場合はビットの位置を直接インデックスで指定できた方が便利かもしれません。
read_bit_at = fn
uint8, at when (uint8 &&& (1 <<< at)) == 0 -> 0
_, _ -> 1
end
# 最下位ビットから4番目のビットがオンであるか確認
1 = 0b00001000 |> read_bit_at.(3)
0 = 0b00000000 |> read_bit_at.(3)
ビット列のパターンマッチ
ビット列のパターンマッチでビットを読むこともできます。
<<_::4, x::1, _::3>> = <<0b00001000>>
1 = x
<<_::4, x::1, _::3>> = <<0b00000000>>
0 = x
get_bit_at = fn bits, index when is_bitstring(bits)->
<<_::size(index + 1), bit::1, _::bitstring>> = bits
bit
end
1 = <<0b00001000>> |> get_bit_at.(3)
0 = <<0b00000000>> |> get_bit_at.(3)
直接今回の内容に関係がありませんが、文字列・文字リスト等の似通ったデータ型でよく混乱するので、ついでにそれらの資料もリンクします。