ビット演算子は複数の真偽状態を1つの整数で効率よく扱える道具です。
初めてでも安心して読めるように、ビットと2進数の基本から、フラグ(ビットマスク)の設定・解除・確認の実例まで、順序立てて解説します。
小さなサンプルコードで動きを確かめながら、基礎を確実に身につけましょう。
ビット演算子の基礎
ビットと2進数の基本
なぜビットで考えるのか
コンピュータは0と1の並び(ビット)で情報を表現します。
複数のオン・オフ状態をひとまとめに持ち運ぶには、ビット単位の表現が最もコンパクトで高速です。
例えば4つの状態を4個の真偽値で持つのではなく、1つの整数(4ビット)にまとめられます。
2進数の見方
10進数の13は、2進数では1101です。
左から順に8、4、2、1の重みを持ち、1101は8+4+0+1=13を意味します。
各ビットが独立したスイッチのようにオン(1)かオフ(0)を示す、と理解すると扱いやすくなります。
例(8ビットの表示)
8ビットの値0101 0011は、上位から「64+16+2+1=83」を表します。
この「各ビットが独立」という性質を利用して、1つの整数の中で複数のフラグを同時に管理できます。
ビット演算子の種類
主な演算子一覧
ビット演算子には、ビット同士を合成したり、反転したり、位置をずらすためのものがあります。
最低限、AND・OR・XOR・NOT・左シフト・右シフトの6種を押さえれば実用に足ります。
以下はJavaScriptでの例ですが、CやJavaなどでも同様です。
| 演算子 | 名前 | 役割 | 例と結果(2進数) |
|---|---|---|---|
| & | AND | 両方が1なら1 | 1101 & 1011 = 1001 |
| | | OR | どちらかが1なら1 | 1101 | 1011 = 1111 |
| ^ | XOR | 異なると1 | 1101 ^ 1011 = 0110 |
| ~ | NOT | 反転 | ~0000 1111 = 1111 0000 |
| << | 左シフト | 左にずらす(×2のn乗) | 0001 << 3 = 1000 |
| >> | 右シフト | 右にずらす(÷2のn乗) | 1000 >> 3 = 0001 |
ANDは選び出し、ORは足し合わせ、XORは切り替え、NOTは反転、シフトは桁移動と覚えておくと実務で迷いません。
初心者が混同しやすい記号
論理演算の&&や||と、ビット演算の&や|は別物です。
ビット単位の合成には&や|を、真偽値どうしの条件評価には&&や||を使います。
真偽値との違い
何が違うのか
真偽値はtrueかfalseの1つだけを持ちますが、ビット値は複数ビットを一度に持てます。
つまりビットは「複数の真偽値を圧縮して持つ」表現で、1つの整数に多数の状態を格納できます。
例で比較
- 論理演算: isReady && isVisible
- ビット演算: (flags & VISIBLE) != 0
前者は2つのbooleanを評価、後者は整数flagsの中からVISIBLEビットが立っているかを取り出して確認しています。
使い分けの目安
保持したい状態が3つ以上になったら、フラグ(ビット)でまとめるとコードがすっきりします。
配列やオブジェクトにbooleanを並べるより、ビットで持つ方が比較や保存が軽量です。
フラグ管理の基本とビットマスク
フラグとは
定義
フラグとは、ある条件が有効か無効かを示す1ビットの印です。
1つの整数の各ビットを別々のフラグとして割り当て、複数の状態を同時に表現します。
簡単なイメージ
「見える」「書ける」「実行できる」「隠す」などの状態を、それぞれ1ビットで管理します。
1ならオン、0ならオフという単純なルールなので、人にも機械にも扱いやすいのが特徴です。
ビットマスクの定義
マスクとは
ビットマスクは、関心のあるビット位置を1にした値です。
AND(&)で「必要なビットだけを取り出すフィルター」として機能します。
例(JS)
JavaScriptで定数を定義します。
// それぞれ固有のビット(2の累乗)を割り当てる
const READ = 1 << 0; // 0001
const WRITE = 1 << 1; // 0010
const EXEC = 1 << 2; // 0100
const HIDDEN = 1 << 3; // 1000
各フラグは必ず「2の累乗(1,2,4,8,…)」にし、ビット位置が被らないようにします。
フラグを設定する
ORで立てる
あるフラグをオンにするにはOR(|)を使います。
let flags = 0;
flags |= READ; // READをオン
flags |= WRITE; // WRITEをオン
// flagsは 0000 -> 0001 -> 0011
ORは「足し合わせる」イメージで、既存のビットを壊さずに新しいビットを追加できます。
フラグを解除する
ANDとNOTで落とす
オフにするときはAND(&)とNOT(~)を組み合わせます。
flags &= ~WRITE; // WRITEをオフにする
// ~WRITE は 1101、ANDで該当ビットだけ0に
解除は「消したいビット位置だけ0、他は1のマスク」を作ってANDする、と覚えましょう。
フラグを切り替える
XORでトグル
オンならオフに、オフならオンにする切り替えにはXOR(^)が便利です。
flags ^= HIDDEN; // HIDDENをトグル
XORは「異なっていれば1」なので、同じ操作をもう一度行うと元に戻る特性があります。
フラグを確認する
ANDでチェック
フラグが立っているかは、AND(&)の結果が0かどうかで判断します。
if ((flags & READ) != 0) {
console.log("読めます");
}
「等しいか」ではなく「0でないか」を見るのがポイントです。
初期化とデフォルト
すべてオフや複数オンで開始
初期値0は全フラグオフを意味します。
複数を既定でオンにしたい場合はORでまとめます。
let flags = READ | WRITE; // 既定で読み書き可
初期化時に「何がオンか」を定数で明示しておくと、後から仕様が変わっても修正が安全です。
実践パターン
複数フラグの同時設定
複数をまとめて立てる
一度に複数のフラグをオンにするにはORで合成します。
flags |= READ | EXEC; // READとEXECを同時にオン
「合成してから一度に適用」の形は、分岐や関数引数にも流用しやすく、意図が読みやすくなります。
条件分岐でのフラグチェック
ANDの結果を明確に比較
分岐で使う時は括弧を付けて可読性を高めます。
if ((flags & (READ | WRITE)) != 0) {
console.log("読み書きのどちらかが可能");
}
if ((flags & (READ | WRITE)) === (READ | WRITE)) {
console.log("読み書きの両方が可能");
}
「いずれかが立っている」か「全てが立っている」かで比較方法が変わる点を押さえましょう。
権限や状態の管理に使う例
権限フラグのサンプル
実用では、権限やUI状態、ゲームの状態などをまとめて持ちます。
例として権限を表にします。
| フラグ名 | 値(10進) | ビット(2進) | 意味 |
|---|---|---|---|
| READ | 1 | 0001 | 読み取り |
| WRITE | 2 | 0010 | 書き込み |
| EXEC | 4 | 0100 | 実行 |
| HIDDEN | 8 | 1000 | 非表示 |
このように一覧化しておくと、レビューやデバッグ時に「どのビットが何を意味するか」が即座に共有できます。
実コード例
function canRead(flags) { return (flags & READ) != 0; }
function canWrite(flags) { return (flags & WRITE) != 0; }
const userFlags = READ | WRITE;
if (canRead(userFlags) && canWrite(userFlags)) {
console.log("読み書き可能");
}
チェック処理を関数化すると、式の重複を減らしバグの混入を防げます。
enumや定数で読みやすくする
定数オブジェクト(JS)
JavaScriptでは定数名で意味を表すのが基本です。
const Perm = Object.freeze({
READ: 1 << 0,
WRITE: 1 << 1,
EXEC: 1 << 2,
HIDDEN: 1 << 3,
});
// 使用例
let flags = Perm.READ | Perm.WRITE;
名前付き定数にするだけで「何のビットか」が一目で分かり、マジックナンバーを排除できます。
補足(PythonのIntFlag)
Pythonならenum.IntFlagが使えます。
from enum import IntFlag, auto
class Perm(IntFlag):
READ = auto() # 1
WRITE = auto() # 2
EXEC = auto() # 4
HIDDEN = auto() # 8
flags = Perm.READ | Perm.WRITE
if flags & Perm.READ:
print("読めます")
言語に応じてenumや属性を活用すると、型安全性と自己文書性が高まります。
シフト演算でフラグを増やす
ビット位置をずらして定義
増えるたびに値を数える必要はありません。
1を左にシフトすれば新しいビットが作れます。
const FLAG0 = 1 << 0;
const FLAG1 = 1 << 1;
const FLAG2 = 1 << 2;
// あるいはループで自動生成も可
「1を左にn回シフト」で2のn乗になるので、ビット位置の重複を自然と回避できます。
よくあるミスと注意点
フラグ値の重複を避ける
2の累乗にする理由
例えばREAD=1、WRITE=2、EXEC=4のように、必ず2の累乗にします。
重複する値(例: READ=1、WRITE=1)や連番(1,2,3)は誤判定の原因になります。
ダメな例
// 悪い例: WRITEが3だと 0011 でREADと被る
const READ = 1; // 0001
const WRITE = 3; // 0011 ←重複
WRITEが立っている時にREADも立っていると判定されてしまい、意図しない結果になります。
優先順位は括弧で明確にする
比較よりANDを先に
演算子の優先順位を誤るとバグになります。
必ず「(flags & MASK) != 0」のように括弧でAND部分を明示しましょう。
// 良い
if ((flags & READ) != 0) { /* ... */ }
// 悪い(読み手に不親切、言語差で誤動作の恐れ)
if (flags & READ != 0) { /* ... */ }
括弧は可読性のためだけでなく、評価順を固定して安全にする役割もあります。
ビット幅と符号に注意
JavaScriptの32ビット制約
JavaScriptのビット演算は内部的に32ビット符号付き整数で行われます。
31個を超えるフラグ(最上位ビット含む)や大きな左シフトは負数になり得るため注意が必要です。
// 31ビット目(1<<31)は負数に見える
console.log(1 << 31); // -2147483648
// 対策: 使うビット数を抑える、またはBigIntを使う
const A = 1n << 40n; // BigInt同士ならOK
多数のフラグが必要ならBigIntを検討するか、用途別に複数のflags変数に分ける設計を選びましょう。
==で比較しない((flags & X) != 0で確認)
等値比較が危険な理由
複数フラグが立つ可能性があるのに「flags == READ」のように書くと、READ以外のビットが立った瞬間に偽になります。
「そのビットが立っているか」を見るなら常に(flags & READ) != 0の形で確認します。
const flags = READ | WRITE;
if (flags == READ) {
// ここには来ない(WRITEも立っているため等しくない)
}
if ((flags & READ) != 0) {
// ここに来る(READビットは立っている)
}
等値比較は「それしか立っていない」を意味しますが、通常は「それが含まれているか」を知りたいはずです。
マジックナンバーを避ける
名前を付けて意味を共有
コード中に直接1や2や4を書くと意図が不明瞭です。
必ず定数やenum名で表し、定義を1か所に集約して保守しやすくします。
// 悪い
if ((flags & 4) != 0) { /* 4って何? */ }
// 良い
if ((flags & EXEC) != 0) { /* 実行権限 */ }
定数名は仕様書の言葉と揃えると、開発者間の認識がずれにくくなります。
まとめ
ビット演算子は、複数のオン・オフ状態を1つの整数で素早く扱う強力な道具です。
基礎として、フラグは2の累乗で定義し、設定はOR、解除はANDとNOT、切り替えはXOR、確認は(flags & MASK) != 0という型を確実に覚えましょう。
論理演算(&&, ||)と混同せず、括弧で評価順を明確にすれば、読みやすく安全なコードになります。
最後に、マジックナンバーを避けて定数やenumを使い、ビット幅や符号の制約(JSは32ビット)に気をつけることで、初学者でも実務で使える堅牢なフラグ管理を実現できます。
