UNISIA-SE Tech Blog

気まぐれお勉強日記

[Python] [基本] ビット演算子まとめ ( |, ^, &, ~, <<, >>)

1. 前提条件


このページでは、Pythonで使用するビット演算子の基本的な使い方について記載する。

▼ Python3.6がインストールされていること。
このページでは、venvの仮想環境(Python3.6)上にNumPyをインストールした環境で、Python対話モード(Pythonインタプリタ)にて実装サンプルを記載している。

※ Python対話モードについては下記を参考。
Python対話モード:[Python] 対話モード (インタプリタ) の使用方法

2. ビット演算子の種類


ビット演算子 は、2進数 で表した int 型 の各ビットに対する演算子で、論理演算や反転、シフト等、以下の種類がある。

▼ ビット演算子一覧
演算子 説明
a | baとbのビット単位の論理和
a ^ baとbのビット単位の排他的論理和
a & baとbのビット単位の論理積
~ aaのビット単位の反転(否定)
a << baをbビット分、左にシフト
a >> baをbビット分、右にシフト

以降、ビット演算子に関する簡単な実装サンプルを対話モード(インタプリタ)で解説する。

3. 論理和 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
 >>>

4. 排他的論理和 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
 >>>

5. 論理積 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
 >>>

6. 反転 NOT ( ~ )


~ a は、反転 した結果を返すが、一般的な 各ビットをそのまま反転した値 を返すわけではなく、この 反転(※1) した値に加え、その 2の補数(※2) に -1 を掛けた値 (符号反転) を返す。

※1 以降、この一般的な反転結果のことを利便上、単純反転と呼ぶ。
※2 以降、2の補数を利便上、補数と呼ぶ。
(2の補数は、対象値の有効桁数 + 1 で2をべき乗した数から 対象値 を減算した結果となる。)

これは、Pythonの int型 に上限、下限がなく(CPUに依存する) 、上の桁が無限に 0 となる想定 であるため、反転した後も 上の桁が無限に 1 となる。

そのため、より短い表現で済むように 補数 -1 を掛けた値 (符号反転) で表現している。
※ 桁数が限定された演算において、対象の値対象値に -1 を掛けた値 (符号反転) は、同値 となり上記は、この性質を利用し表現している。

結局 ~ aa を単純反転した補数に -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 )\) となっている。
10進数:\( ~5 =  - ( 5 + 1 ) = - 6 \)
2進数:\( ~0b0101 =  - ( 0b0101 + 1 ) = - 0b0110 = - 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
 >>> # ① 演算結果 (10 進数) ※ - ( a + 1 ) と一致
 >>> print(bit_c)
 -6
 >>> # ② 演算結果 (2 進数) ※ - ( a + 1 ) と一致
 >>> print(bin(bit_c))
 -0b110
 >>>

上記 ①、②の通り、反転結果は \(- ( a + 1 )\) となっている。
2進数:\( ~0b0101 =  - ( 0b0101 + 1 ) = - 0b0110 = - 0b110 \)
10進数:\( ~5 =  - ( 5 + 1 ) = - 6 \)

※ これは、上記で触れた同値の性質より、対象が4桁でなくても補数をどこまで大きく考えても同じ結果となる。
以下に示す通り、8桁まで範囲を広げ、単純反転しても補数を取るため、結果は変わらない。
  (1) \( 0b00001010 \) ※ 2進数 0101 (10進数:5)
  (2) \( 0b11110101 \) ※ (1)の単純反転
  (3) \( 0b100000000-0b11110101 = 0b110\) ※ (2)の補数
  (4) \( -1 * 0b110 = - 0b110\) ※ (3)に \(-1\) を掛ける(符号反転)

7. 左シフト ( << )


a << b は、b の桁数分 左にシフトし、空白となった右側の桁は 0 で埋められる。

※ 使用用途
元の値を 2のべき乗(+)倍 する場合に使用する。
※左 に1シフトすると 2倍(2の1乗)、2シフトすると 4倍(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
 >>>

8. 右シフト ( >> )


a >> b は、b の桁数分、右にシフトし、空白となった左側の桁は 0 で埋められ、最下位より右に溢れたビットは破棄される。

※ 使用用途
元の値を 2のマイナスべき乗倍 する場合に使用する。
※ 右に1シフトすると 1/2倍(2の-1乗)、2シフトすると 1/4倍(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の極意』株式会社昭和システム


Copyright UNISIA-SE All Rights Reserved.
s-hama@unisia-se.jp