6.

Python ビット演算子|& | ^ ~ とシフト・任意精度 int・enum.Flag

編集
この記事の要点
  • ビット演算子は &(AND)|(OR)^(XOR)~(NOT)<< >>(シフト)
  • Python の int は任意精度<< でオーバーフローせず、>> は符号を保つ算術シフト
  • ~x-x - 1 と等しい。2 の補数として無限桁の符号ビットが続くと考える
  • フラグ管理は enum.Flag を使うと & | をそのまま安全に書ける
  • 論理演算は and / or& | と混同しない(論理演算子

一覧

演算子意味結果
&AND(両方 1 なら 1)0b1100 & 0b10100b1000 = 8
|OR(どちらか 1 なら 1)0b1100 | 0b10100b1110 = 14
^XOR(異なれば 1)0b1100 ^ 0b10100b0110 = 6
~NOT(ビット反転)~12-13
<<左シフト(2 倍)3 << 212
>>右シフト(1/2 倍)12 >> 23

^べき乗ではありません。べき乗は ** です。

a, b = 0b1100, 0b1010          # 12, 10
print(bin(a & b))               # 0b1000
print(bin(a | b))              # 0b1110
print(bin(a ^ b))              # 0b110
print(bin(a << 2))             # 0b110000
print(bin(a >> 2))             # 0b11

print(f"{a:08b}")              # 00001100   桁を揃えて表示
print(int("1100", 2))          # 12         2 進文字列から int

~ と負数の考え方

Python の int には桁数の上限が無いため、負数は「上位に符号ビットが無限に続く 2 の補数」として扱われます。

print(~12)          # -13     ~x == -x - 1
print(~0)           # -1
print(~-1)          # 0

# 32bit の値として見たいときはマスクする
x = -13
print(x & 0xFFFFFFFF)            # 4294967283
print(f"{x & 0xFFFFFFFF:032b}")   # 11111111111111111111111111110011

シフトの挙動

print(1 << 100)      # 1267650600228229401496703205376   桁あふれしない
print(-8 >> 1)       # -4    符号を保つ(算術シフト)
print(-1 >> 10)      # -1    何回シフトしても -1 のまま

# 負のシフト量は例外
# print(1 << -1)
# ValueError: negative shift count

Java の >>>(符号なし右シフト)に相当する演算子はありません。必要ならマスクしてから右シフトします。

フラグ管理

READ, WRITE, EXEC = 1, 2, 4          # 0b001, 0b010, 0b100

perm = READ | WRITE                   # 立てる
print(perm)                           # 3

print(bool(perm & WRITE))              # True    立っているか
perm &= ~WRITE                         # 下ろす
print(bool(perm & WRITE))              # False
perm ^= READ                          # 反転
print(perm)                           # 0

実務では enum.Flag を使うほうが安全で読みやすくなります。

from enum import Flag, auto

class Perm(Flag):
    READ = auto()
    WRITE = auto()
    EXEC = auto()

p = Perm.READ | Perm.WRITE
print(p)                    # Perm.READ|WRITE
print(Perm.WRITE in p)      # True
print(p & Perm.EXEC)         # Perm.0   (空)

集合演算にも同じ記号を使う

a, b = {1, 2, 3}, {2, 3, 4}
print(a & b)      # {2, 3}        積集合
print(a | b)      # {1, 2, 3, 4}  和集合
print(a ^ b)      # {1, 4}        対称差
print(a - b)      # {1}           差集合

# 辞書のマージは | (Python 3.9 以降)
print({"a": 1} | {"b": 2})   # {'a': 1, 'b': 2}

よく使うイディオム

n = 12
print(n & 1 == 0)          # True    偶数判定(n % 2 == 0 でも可)
print(n & (n - 1) == 0)    # False   2 のべき乗か(8 なら True)
print(bin(n).count("1"))   # 2       立っているビット数
print(n.bit_count())       # 2       Python 3.10 以降はこちら
print(n.bit_length())      # 4       表現に必要なビット数

x, y = 3, 5
x, y = y, x                # 交換は XOR ではなくアンパックで書く

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 算術演算子
  2. 文字列演算子
  3. 代入演算子
  4. 比較演算子
  5. 論理演算子
  6. ビット演算子

最近更新/作成されたページ