SIGMA-SE Math & Tech Library

SIGMA-SE Math & Tech Library


数学と情報技術をテーマに、書籍や教材だけではつかみにくい考え方を具体例とともに簡潔にわかりやすく伝える解説サイトです。
技術の歴史や背景、関連知識の整理、学習のための覚書や要約記事も掲載しています。

Python - ビット演算子:OR・XOR・AND・反転・シフトの基本

概要

Pythonのビット演算子を使い、整数を2進数のビット列として扱う考え方を整理する。

ビット演算は、普段の業務コードでは頻度が高くない一方で、フラグ管理、権限管理、低レイヤ処理、暗号やネットワークの理解で重要になる。

ここでは、OR、XOR、AND、反転、左シフト、右シフトの結果を、2進数の動きとあわせて確認する。

この記事の構成

各ビット演算子の使い方と実装サンプル

ビット演算子の種類

ビット演算子は、\(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 ^ 0b11110b1010を得る。これは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 は、abビット右へシフトする。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)として考えると理解しやすい。

参考文献

この記事を共有
Xで共有 Facebookで共有 LINEで共有



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