概要
Pythonの整数に対するOR、XOR、AND、反転、左右のシフトを整理する。
読み取り・書き込み・実行の3種類を説明用のフラグに割り当て、設定・反転・判定・解除の違いを追う。同じ操作を繰り返した結果も比べ、目的に合う演算子を選ぶ。フラグの比較例はPython 3.12.2で確認。
この記事の構成
- ビット演算子の種類
ビット演算子の種類と各項目の特徴を整理。 - 論理和 OR( | )
ビットを立てる操作と同じフラグを再設定した結果を確認。 - 排他的論理和 XOR( ^ )
反転を2回行うと元へ戻ることを設定操作と比較。 - 論理積 AND( & )
フラグの有無と複数フラグをすべて満たす条件を確認。 - 反転 NOT( ~ )
必要なフラグだけを解除し、固定幅の反転と区別。 - 左シフト( << )
左シフト( << )の意味と要点を具体例から整理。 - 右シフト( >> )
右シフト( >> )の意味と要点を具体例から整理。
各ビット演算子の使い方と実装サンプル
ビット演算子の種類
ビット演算子は、\(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 >>> -
名前のあるフラグを設定する
ここでは説明用にREAD = 0b001、WRITE = 0b010、EXECUTE = 0b100を割り当てる。OSの権限設定を直接変更するコードではない。READ, WRITE, EXECUTE = 0b001, 0b010, 0b100 flags = READ print(f"{flags:03b}") flags |= WRITE print(f"{flags:03b}") flags |= WRITE print(f"{flags:03b}")001 011 01103bは少なくとも3桁の2進数として表示する指定である。ORはすでに1の桁を1のまま保つので、同じフラグを再設定しても解除されない。
排他的論理和 XOR( ^ )
a^b は、ビット単位(同じ桁同士)で双方が一致すれば \(0\) を返し、違っていれば \(1\) を返す。
※ 使用用途
特定のビットだけを反転させたい場合は、その位置を \(1\)、変更しない位置を \(0\) としたマスクとのXORを取る。
-
実装サンプル
$ python >>> # 1111 のうち、マスクが1となる先頭2桁だけを反転する。 >>> bit_a = 0b1111 >>> bit_b = 0b1100 >>> bit_c = bit_a ^ bit_b >>> # 演算結果 (10 進数) >>> print(bit_c) 3 >>> # 演算結果 (2 進数) >>> print(bin(bit_c)) 0b11 >>> -
XORで2回反転すると元に戻る
READ, WRITE = 0b001, 0b010 flags = READ flags ^= WRITE print(f"{flags:03b}") flags ^= WRITE print(f"{flags:03b}")011 001XORは現在の値を反転する操作なので、2回目は書き込みフラグが消える。「書き込みを許可する」にはOR、「書き込みの状態を切り替える」にはXORというように目的で選ぶ。
論理積 AND( & )
a&b は、ビット単位(同じ桁同士)で互いに \(1\) である場合 \(1\) を返し、互いに \(1\) でない場合 \(0\) を返す。
※ 使用用途
特定のビットを強制的に \(0\)(OFF)としたい場合、対応するマスクを \(0\)、残したい位置を \(1\) としてANDを取る。
-
実装サンプル
$ 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 >>> -
一つでも立っているか、すべて立っているかを分ける
READ, WRITE, EXECUTE = 0b001, 0b010, 0b100 flags = READ | WRITE required = READ | EXECUTE print(bool(flags & READ)) print(bool(flags & EXECUTE)) print(bool(flags & required)) print((flags & required) == required)True False True Falseflags & requiredがゼロでなければ必要なビットのうち少なくとも一つが立っている。すべてを要求する場合はANDの結果がrequiredと一致するかを調べる。この例ではREADだけが一致し、EXECUTEがないため最後の判定は偽になる。
反転 NOT( ~ )
Pythonのint型は固定ビット幅を持たず、ビット演算は符号ビットが無限に続く \(2\) の補数表現として計算される。このため、すべてのビットを反転する~aは常に-(a + 1)、すなわち-a - 1となる。固定幅のレジスタや通信データだけを反転したい場合は、必要な幅のマスクとのXORを使う。
-
実装サンプル :
~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 >>>
例えば \(4\) ビット幅に限定して \(0101\) だけを反転する場合は、0b0101 ^ 0b1111で0b1010を得る。これはPythonの無限幅の~0b0101とは目的が異なる。
-
他のフラグを残して書き込みだけを解除する
READ, WRITE, EXECUTE = 0b001, 0b010, 0b100 flags = READ | WRITE | EXECUTE flags &= ~WRITE print(f"{flags:03b}") print(bool(flags & WRITE)) print((flags & (READ | EXECUTE)) == (READ | EXECUTE))101 False True~WRITEは書き込みの桁を0、それ以外を1にするマスクとして働く。ANDを取ると対象の桁だけが消える。今回は非負整数のフラグを扱っているため、結果もそのフラグの範囲に収まる。OR・AND・XORと集合の関係は離散数学の集合の説明も参照。
左シフト( << )
a << bは整数aを非負整数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 は、aをbビット右へシフトする。Pythonではa // (2 ** b)と同じ結果になり、正数では左側を \(0\) で補うように見える。負数では符号を保つように \(1\) が続くため、常に \(0\) 埋めとはならない。
※ 使用用途
元の整数を \(2^b\) で切り下げ除算する場合に使用する。
-
実装サンプル
$ 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 >>> >>> # ※割り切れない場合 >>> # 0b10100011(10 進数で163)を右に2シフトする >>> bit_c = 0b10100011 >>> bit_d = bit_c >> 2 >>> # 演算結果 (10 進数) >>> print(bit_d) # 163 // 4 と同じく、負の無限大方向へ丸める 40 >>> # 演算結果 (2 進数) >>> print(bin(bit_d)) # 右に2シフトし、最下位より右に溢れたビット(11)は破棄となる 0b101000 >>>
シフト数は非負整数で指定する。負のシフト数はValueErrorとなる。
まとめ
- フラグを設定するにはOR、切り替えるにはXOR、残っているビットを調べるにはANDを使う。
- 複数フラグのうち一つでも立っている条件と、すべて立っている条件を区別する。
- flags &= ~maskで指定したビットだけを解除できる。Pythonの整数は固定ビット幅ではなく、~xは-(x + 1)となる。
- 非負整数bによる左シフトは2 ** b倍、右シフトは2 ** bによる切り下げ除算に対応する。
参考文献
- 金城 俊哉(\(2018\))『現場ですぐに使える! Pythonプログラミング逆引き大全313の極意』株式会社昭和システム
- Python公式ドキュメント - 整数型におけるビット単位演算(日本語・整数のビット演算の公式解説)
- Python公式ドキュメント - 二項ビット演算(日本語・ビット演算子の公式仕様)