SIGMA-SE Tech Blog

SIGMA-SE Tech Blog

Python - ビット演算子まとめ : |, ^, &, ~, <<, >>

目的

この記事では、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\) を掛けた値(符号反転)は、同値となり上記は、この性質を利用し表現している。

結局、~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\) を掛けた値(符号反転)が \(- ( 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\) 桁まで範囲を広げ、単純反転しても補数を取るため、結果は変わらない。

  1. \(0b00001010\) ※ \(2\)進数 \(0101\)\((\)\(10\)進数:\(5\)\()\)
  2. \(0b11110101\) ※「1.」 の単純反転
  3. \(0b100000000-0b11110101\) = \(0b110\) ※「2.」の補数
  4. \(-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の極意』株式会社昭和システム


Copyright SIGMA-SE All Rights Reserved.
s-hama@sigma-se.jp