ビットAND演算でフラグ判定
真偽値を 8 個ならべて持ち回るのはつらい
通知設定を考えます。メールを送るか、プッシュを出すか、SMS を出すか。真偽値で 1 つずつ持つと、設定が増えるたびに引数もテーブルの列も増えていきます。10 個になれば 10 個分です。
けれどこれは、オンかオフかが 10 個並んでいるだけです。2 進数の 1 桁が表せるのも、まさにオンかオフでした。つまり整数 1 個の中に、設定を何個でも詰め込めます。詰めたあとに要るのが、「この桁は立っているか」を取り出す道具です。
AND は、桁ごとに独立して計算する
& は 2 つの数を桁ごとに見比べて、両方が 1 の桁だけ 1 を残します。片方でも 0 なら 0 です。
プレーンテキスト
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1大事なのは、桁と桁がまざらないことです。足し算と違って繰り上がりが起きません。1101 & 1010 は、左から 1&1、1&0、0&1、1&0 をそれぞれ計算して 1000 になります。隣を気にせず 4 回の独立した計算をしているだけです。
見たい桁だけ 1 のマスクを当てる
両方が 1 のときだけ残る、ということは、片方を「見たい桁だけ 1、ほかは全部 0」にしておけば、結果に残るのはその桁の情報だけになります。この相手役をマスクと呼びます。
Python
MAIL = 0b001
PUSH = 0b010
SMS = 0b100
settings = 0b101 # メールと SMS だけオン
print(settings & MAIL) # 1
print(settings & PUSH) # 0
print(settings & SMS) # 4マスクの正体は、その桁の重みそのものです。PUSH は右から 2 桁目なので 2、SMS は 3 桁目なので 4。桁の重みを並べた表の数が、そのままマスクの値になります。
結果は 0 かどうかで見る
上の出力をもう一度見てください。SMS はオンなのに、返ってきたのは 1 ではなく 4 でした。残るのはその桁の重みなので、上の桁ほど大きい数になります。
ここで == 1 と比べてしまうと、いちばん右の桁以外は全部オフ扱いになります。しかもいちばん右の桁だけはたまたま通るので、テストの並び順によっては最後まで気づきません。判定は必ず「0 かどうか」で書いてください。
現場の話 UNIX の権限
chmod 755の7は、読み4、書き2、実行1を足した数です。権限 & 2が0でなければ書き込める、という判定が今も動いています。
要件
- n と k は 0 以上の整数と仮定して良い (k は 30 未満)
- ビット演算 & と << を使うこと
- 戻り値は boolean (True/False or true/false) で返すこと
入出力例
isBitSet(13, 0) → true
isBitSet(13, 1) → false
isBitSet(13, 2) → true
isBitSet(13, 3) → true
isBitSet(13, 4) → false
isBitSet(0, 0) → false
isBitSet(1024, 10) → true