Contents
ビットシフト演算子の概要と実践的な使い方
1️⃣ シフト演算子そのものの動作
| 演算子 | 意味 | Python の挙動(公式) |
|---|---|---|
<< |
左へ n ビット 移動 | 下位ビットは必ず 0 で埋められ、結果は x * (2**n) と等価です。※整数は任意長なのでオーバーフローは発生しません。 |
>> |
右へ n ビット 移動 | 符号付き整数では算術シフトが行われ、符号ビットがコピーされます。結果は x // (2**n) と等価です。 |
参考: Python 公式ドキュメント – Shifting Operations
https://docs.python.org/3/reference/expressions.html#shifting-operations
1‑1️⃣ 左シフトの例
|
1 2 3 |
x = 0b0011_0101 # 53 print(bin(x << 2)) # 0b11010100 → 下位に 00 が付く |
x << 2 は x * 4 と同じ数値になります。
1‑2️⃣ 右シフト(算術シフト)の例
|
1 2 3 |
neg = -0b0010_1000 # -40 print(neg >> 2) # -10 (= -40 // 4) |
負の整数でも符号が保持され、除算 // と同様に切り捨てられます。
2️⃣ 「シフトは掛け算/割り算より速い?」という誤解を正す
昔ながらの組み込み言語(C やアセンブリ)では、ビットシフトが乗算・除算に比べて必ずしも高速とは限りません。現代の CPU は 2 の累乗倍の乗算・除算を単一命令で実行できることが多く、コンパイラや JIT が最適化して x << n を x * (1<<n) に置き換えるケースもあります。
Python(CPython)では整数演算は C の PyLongObject に委譲されており、内部的に 乗算とシフトのどちらが速いかは実装次第 です。したがって
「左シフトは必ず掛け算より高速」
→ 誤解を招く表現 であり、実際には「等価な計算結果を得られる」とだけ述べる方が安全です。
3️⃣ ビットマスクによるフラグ管理
3‑1️⃣ 基本的なビット定数の作り方
|
1 2 3 4 5 |
READ = 1 << 0 # 0b0001 WRITE = 1 << 1 # 0b0010 EXECUTE = 1 << 2 # 0b0100 ADMIN = 1 << 3 # 0b1000 |
1 << n が 2ⁿ のビット位置を表すため、フラグが増えてもコードはシンプルです。
3‑2️⃣ フラグ操作の実例
|
1 2 3 4 5 6 7 8 9 |
perm = READ | WRITE # 許可: 読み取り+書き込み (0b0011) # 判定 has_write = bool(perm & WRITE) # True # 追加・削除 perm |= EXECUTE # 0b0111 に拡張 perm &= ~WRITE # 書き込み権限を除去 → 0b0101 |
3‑3️⃣ enum.Flag を使った可読性向上(Python 3.6+)
|
1 2 3 4 5 6 7 8 9 10 11 12 |
from enum import Flag, auto class Permission(Flag): READ = auto() WRITE = auto() EXECUTE = auto() ADMIN = auto() user_perm = Permission.READ | Permission.WRITE if Permission.ADMIN in user_perm: print("管理者です") |
enum.Flag は内部でビット演算を行うので、可読性と型安全性 が同時に得られます。
4️⃣ 複数フィールドのパック & アンパック
4‑1️⃣ RGB カラーコード(24 ビット)
|
1 2 3 4 5 6 7 8 9 10 |
def pack_rgb(r: int, g: int, b: int) -> int: """0〜255 の各成分を 24bit 整数に詰め込む""" return (r << 16) | (g << 8) | b def unpack_rgb(v: int): r = (v >> 16) & 0xFF g = (v >> 8 ) & 0xFF b = v & 0xFF return r, g, b |
シフトとマスクだけで 固定幅フィールド を安全に扱えます。
4‑2️⃣ 任意ビット幅のデコード例(ステータス + エラーレベル)
|
1 2 3 4 5 6 7 8 9 |
def decode(word: int): # 上位 3 ビット : status, 下位 5 ビット : error_level status = (word >> 5) & 0b111 # 3ビット取得 error_level = word & 0b1_1111 # 5ビット取得 return status, error_level packed = 0b101_11001 # status=5, error_level=25 print(decode(packed)) # (5, 25) |
ネットワークプロトコルやバイナリファイルのレイアウト設計で頻出するテクニックです。
5️⃣ 循環シフト(ローテート)の実装と応用
5‑1️⃣ 32 ビット幅を想定した左右ローテート
|
1 2 3 4 5 6 7 8 9 10 |
MASK_32 = 0xFFFFFFFF def rotl32(v: int, n: int) -> int: n &= 31 # シフト数は 0~31 に正規化 return ((v << n) | (v >> (32 - n))) & MASK_32 def rotr32(v: int, n: int) -> int: n &= 31 return ((v >> n) | (v << (32 - n))) & MASK_32 |
mask により 任意長整数 を 32 ビットに切り詰めます。
5‑2️⃣ 暗号・ハッシュでの利用例(簡易ハッシュ風)
|
1 2 3 4 5 6 7 8 9 10 11 |
def simple_hash(data: int) -> int: h = 0x811C9DC5 # FNV-1a の初期値 for i in range(4): byte = (data >> (i * 8)) & 0xFF h ^= byte h = rotl32(h, 5) # ビット拡散 h = (h * 0x01000193) & MASK_32 # FNV-1a の乗算部 return h print(hex(simple_hash(0xDEADBEEF))) # → 0x71e3c2d5 (例) |
ローテートは ビットの拡散(diffusion) を高め、暗号学的に重要な性質を提供します。
6️⃣ パフォーマンスと注意点
| 項目 | 内容 |
|---|---|
| 整数サイズ | Python の int は任意長。ビット幅が大きくなるほどオペレーションは O(⌈bits/wordsize⌉) に増加します。 |
| 固定幅との違い | C や Rust などの言語ではオーバーフローが未定義動作になることがありますが、Python は自動で桁上げするため安全です。ただし外部ライブラリ(numpy.int32 等)を使う場合は注意が必要です。 |
| CPU 最適化 | CPython のビットシフトは C の左シフト演算子に直接マッピングされます。一方、乗算は高速なハードウェア命令に置き換えられることが多く、ベンチマークで差が出ないケースが多数あります。 |
| ベンチマーク例(CPython 3.11, x86‑64) | python\nimport timeit\nprint(timeit.timeit('x<<5', setup='x=12345'))\nprint(timeit.timeit('x*32', setup='x=12345'))\n → 差は 1〜2% 程度に留まることが多いです。 |
| JIT 環境 | PyPy や GraalPython では、実行時最適化により x<<n と x*(1<<n) が同一コードへ統合されます。 |
7️⃣ まとめと次のステップ
- シフトは「演算結果が等価」であり、速度面は環境依存です。実装前にベンチマークを取ることを推奨します。
- ビットマスク (
1 << n) と組み合わせた フラグ管理 はメモリ効率・検索速度ともに優れ、enum.Flagで可読性も確保できます。 - 固定幅フィールドの パック/アンパック(例: RGB、ステータスコード)はシフトとマスクだけで実装可能です。通信プロトコルやバイナリファイルの設計に役立ちます。
- ローテート は暗号・ハッシュ以外にも、循環バッファや擬似乱数生成器でも活躍します。
maskを忘れずに使用すれば任意ビット幅で安全に扱えます。 - パフォーマンスが気になる場合は 公式ドキュメント と実際のベンチマーク結果を併せて検討し、必要なら C 拡張や
numpyの固定幅整数型へ移行してください。
これらのテクニックを自分のプロジェクトに組み込むことで、計算速度・メモリ使用量・コード可読性 のバランスが取れた実装が可能になります。ぜひハンズオンで試し、最適なパターンを見つけてください。