SIGMA-SE Math & Tech Library

SIGMA-SE Math & Tech Library


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

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

概要

Pythonの整数に対するOR、XOR、AND、反転、左右のシフトを整理する。

読み取り・書き込み・実行の3種類を説明用のフラグに割り当て、設定・反転・判定・解除の違いを追う。同じ操作を繰り返した結果も比べ、目的に合う演算子を選ぶ。フラグの比較例はPython 3.12.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
        >>>
    
  • 名前のあるフラグを設定する
    ここでは説明用に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
    011
    

    03bは少なくとも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
    001
    

    XORは現在の値を反転する操作なので、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
    False
    

    flags & 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による切り下げ除算に対応する。

参考文献

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



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