目的
この記事では、Pythonで使用するビット演算子の基本的な使い方について記載する。
各ビット演算子の使い方と実装サンプル
ビット演算子の種類
ビット演算子は、\(2\)進数で表したint型の各ビットに対する演算子で、論理演算や反転、シフト等、以下の種類がある。
- ビット演算子一覧
演算子 使用例 説明 | a | b aとbのビット単位の論理和 ^ a ^ b aとbのビット単位の排他的論理和 & a & b aとbのビット単位の論理積 ~ ~a aのビット単位の反転(否定) << a << b aをbビット分、左にシフト >> a >> b aをbビット分、右にシフト
以降、ビット演算子に関する簡単な実装サンプルを対話モード(インタプリタ)で解説する。
論理和 OR( | )
a|b
は、ビット単位(同じ桁同士)で、\(1\) が少なくとも一つ存在すれば \(1\) を返し、\(1\) が存在しなければ \(0\) を返す。
※ 使用用途
特定のビットを強制的に \(1\)(ON)にしたい場合、対応するビットを \(1\) とし、それ以外は、元のままとすることで、特定のビットのみ \(1\) に変更することができる。
- 実装サンプル
$ python >>> # 0100 の 末尾2桁 を 1 (ON) にする >>> bit_a = 0b0100 >>> bit_b = 0b0111 >>> bit_c = bit_a | bit_b >>> # 演算結果 (10 進数) >>> print(bit_c) 7 >>> # 演算結果 (2 進数) >>> print(bin(bit_c)) 0b111 >>>
排他的論理和 XOR( ^ )
a^b
は、ビット単位(同じ桁同士)で双方が一致すれば \(0\) を返し、違っていれば \(1\) を返す。
※ 使用用途
特定のビットを強制的に反転させたい場合、対応するビットに特定ビットの反転値を指定し、それ以外は、元のままとすることで、特定のビットのみ反転させることができる。
- 実装サンプル
$ python >>> # 1111 の 先頭 2桁 は 0 (OFF)に、末尾2桁 は 1 (反転)とする。 >>> bit_a = 0b1111 >>> bit_b = 0b1100 >>> bit_c = bit_a ^ bit_b >>> # 演算結果 (10 進数) >>> print(bit_c) 3 >>> # 演算結果 (2 進数) >>> print(bin(bit_c)) 0b11 >>>
論理積 AND( & )
a&b
は、ビット単位(同じ桁同士)で互いに \(1\) である場合 \(1\) を返し、互いに \(1\) でない場合 \(0\) を返す。
※ 使用用途
特定のビットを強制的に \(0\)(OFF)としたい場合、対応するビットを \(0\) とし、それ以外は、元のままとすることで、特定のビットのみ \(1\) に変更することができる。
- 実装サンプル
$ python >>> # 1111 の 末尾3桁 を 0 (OFF) にする >>> bit_a = 0b1111 >>> bit_b = 0b1000 >>> bit_c = bit_a & bit_b >>> # 演算結果 (10 進数) >>> print(bit_c) 8 >>> # 演算結果 (2 進数) >>> print(bin(bit_c)) 0b1000 >>>
反転 NOT( ~ )
~a
は、反転した結果を返すが、一般的な各ビットをそのまま反転した値を返すわけではなく、この反転(*1)した値に加え、その \(2\) の補数(*2)に \(-1\) を掛けた値(符号反転)を返す。
(*1)以降、この一般的な反転結果のことを利便上、単純反転と呼ぶ。 (*2)以降、2の補数を利便上、補数と呼ぶ。 (2の補数は、対象値の有効桁数 + \(1\) で \(2\) をべき乗した数から対象値を減算した結果となる。)
これは、Pythonのint型に上限、下限がなく(CPUに依存する)、上の桁が無限に \(0\) となる想定であるため、反転した後も上の桁が無限に \(1\) となる。
そのため、より短い表現で済むように補数に \(-1\) を掛けた値(符号反転)で表現している。
※ 桁数が限定された演算において、対象の値と対象値に \(-1\) を掛けた値(符号反転)は、同値となり上記は、この性質を利用し表現している。
結局、~a
も a
を単純反転した補数に \(-1\) を掛けた値(符号反転)も、一般化すると \(- ( a + 1 )\)
で求められるが、その経緯を以降の実装サンプルで解説する。
-
実装サンプル :
~a
が \(- ( a + 1 )\) となる確認
※ \(2\)進数 \(0101\)\((\)\(10\)進数:\(5\)\()\)を反転する。$ python >>> # 0b0101 を反転する >>> bit_a = 0b0101 >>> bit_b = ~bit_a >>> # ① 演算結果 (10 進数) ※ - ( a + 1 ) と一致 >>> print(bit_b) -6 >>> # ② 演算結果 (2 進数) ※ - ( a + 1 ) と一致 >>> print(bin(bit_b)) -0b110 >>>
-
実装サンプル :
a
を単純反転した補数に \(-1\) を掛けた値(符号反転)が \(- ( a + 1 )\) となる確認
※ \(2\)進数 \(0101\)\((\)\(10\)進数:\(5\)\()\)を反転する。$ python >>> # 0b0101 を単純反転する (排他的論理和) >>> bit_a = 0b0101 ^ 0b1111 >>> print(bin(bit_a)) 0b1010 >>> # 0b1010 の補数を求める >>> bit_b = 0b10000 - bit_a >>> print(bin(bit_b)) 0b110 >>> # 0b110 に -1 を掛ける(符号反転) >>> bit_c = -1 * bit_b >>> # (*3)演算結果 (10 進数) ※ - ( a + 1 ) と一致 >>> print(bit_c) -6 >>> # (*4)演算結果 (2 進数) ※ - ( a + 1 ) と一致 >>> print(bin(bit_c)) -0b110 >>>
上記 (*3)、(*4)の通り、反転結果は \(- ( a + 1 )\) となっている。
\(2\)進数:\(0b0101 = - ( 0b0101 + 1 ) = - 0b0110 = - 0b110\)
\(10\)進数:\(5 = - ( 5 + 1 ) = - 6\)
※ これは、同値の性質より、対象が \(4\) 桁でなくても補数をどこまで大きく考えても同じ結果となる。 以下に示す通り、\(8\) 桁まで範囲を広げ、単純反転しても補数を取るため、結果は変わらない。
- \(0b00001010\) ※ \(2\)進数 \(0101\)\((\)\(10\)進数:\(5\)\()\)
- \(0b11110101\) ※「1.」 の単純反転
- \(0b100000000-0b11110101\) = \(0b110\) ※「2.」の補数
- \(-1\) * \(0b110 = - 0b110\) ※「3.」に \(-1\) を掛ける(符号反転)
左シフト( << )
a<<b
は、b
の桁数分 左にシフトし、空白となった右側の桁は \(0\) で埋められる。
※ 使用用途
元の値を \(2\) のプラスべき乗倍する場合に使用する。
※ 左に \(1\) シフトすると \(2\)倍(\(\displaystyle 2^1\)乗)、\(2\)シフトすると \(4\)倍(\(\displaystyle 2^2\)) … \(n\) シフトすると
\(2\)の\(n\)乗倍といった増え方をする
- 実装サンプル
$ python >>> # 0b10101111(10 進数で175)を左に2シフトする >>> bit_a = 0b10101111 >>> bit_b = bit_a << 2 >>> # 演算結果 (10 進数) >>> print(bit_b) # 175の4(2の2乗)倍となる 700 >>> # 演算結果 (2 進数) >>> print(bin(bit_b)) # 左に2シフトし、空白となった右側の2桁は 0 となる 0b1010111100 >>>
右シフト( >> )
a>>b
は、b
の桁数分、右にシフトし、空白となった左側の桁は \(0\) で埋められ、最下位より右に溢れたビットは破棄される。
※ 使用用途
元の値を \(2\) のマイナスべき乗倍する場合に使用する。
※ 右に \(1\) シフトすると \(\displaystyle \frac{1}{2}\)倍(\(\displaystyle 2^{-1}\)乗)、\(2\)シフトすると \(\displaystyle
\frac{1}{4}\)倍(\(\displaystyle 2^{-2}\)乗) … \(n\)シフトすると \(2^{-n}\)乗倍といった増え方をする。
- 実装サンプル
$ python >>> # 0b10000000(10 進数で128)を右に2シフトする >>> bit_a = 0b10000000 >>> bit_b = bit_a >> 2 >>> # 演算結果 (10 進数) >>> print(bit_b) # 128の1/4(2のマイナス2乗)倍となる 32 >>> # 演算結果 (2 進数) >>> print(bin(bit_b)) # 右に2シフトし、最下位より右に溢れたビット(00)は破棄となる 0b100000 >>> >>> # ※割り切れない場合 >>> # 0b10101111(10 進数で163)を右に2シフトする >>> bit_c = 0b10100011 >>> bit_d = bit_c >> 2 >>> # 演算結果 (10 進数) >>> print(bit_d) # 割り切れない場合、小数点第1位を四捨五入 40 >>> # 演算結果 (2 進数) >>> print(bin(bit_d)) # 右に2シフトし、最下位より右に溢れたビット(11)は破棄となる 0b101000 >>>
参考文献
- 金城 俊哉(2018)『現場ですぐに使える! Pythonプログラミング逆引き大全313の極意』株式会社昭和システム