概要
Pythonのビット演算子を使い、整数を2進数のビット列として扱う考え方を整理する。
ビット演算は、普段の業務コードでは頻度が高くない一方で、フラグ管理、権限管理、低レイヤ処理、暗号やネットワークの理解で重要になる。
ここでは、OR、XOR、AND、反転、左シフト、右シフトの結果を、2進数の動きとあわせて確認する。
この記事の構成
- ビット演算子の種類
ビット演算子の種類と各項目の特徴を整理。 - 論理和 OR( | )
論理和 OR( | )の意味と要点を具体例から整理。 - 排他的論理和 XOR( ^ )
排他的論理和 XOR( ^ )の意味と要点を具体例から整理。 - 論理積 AND( & )
論理積 AND( & )の意味と要点を具体例から整理。 - 反転 NOT( ~ )
反転 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 >>>
排他的論理和 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 >>>
論理積 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 >>>
反転 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とは目的が異なる。
左シフト( << )
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 は、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 >>>
まとめ
- andやorは真偽値を扱う論理演算子、&や|は整数の各ビットを扱うビット演算子で、用途が異なる。
- |、^、&はビットごとの比較を行い、左シフトは2倍方向、右シフトは2で割る方向へ値を変化させる。
- Pythonの整数は固定ビット幅ではないため、ビット反転~xは-(x + 1)として考えると理解しやすい。
参考文献
- 金城 俊哉(\(2018\))『現場ですぐに使える! Pythonプログラミング逆引き大全313の極意』株式会社昭和システム
- Python公式ドキュメント - 整数型におけるビット単位演算(日本語・整数のビット演算の公式解説)
- Python公式ドキュメント - 二項ビット演算(日本語・ビット演算子の公式仕様)