コンピューターはすべてのデータを 0 と 1 のビットで扱います。ビット演算はそのビットを直接操作する演算です。フラグ管理・パフォーマンス最適化・低レベルなデータ操作などで使われます。
4 つのビット演算子はビットを 1 本ずつ独立して処理します。
両方のビットが 1 のとき、結果が 1 になります。
1
2
3
4
int a = 0b_1100; // 12
int b = 0b_1010; // 10
Console.WriteLine(a & b); // 8
Console.WriteLine($"{a & b:B4}"); // 1000
どちらか一方でも 1 なら結果が 1 になります。
1
2
3
int a = 0b_1100;
int b = 0b_1010;
Console.WriteLine(a | b); // 14
2 つのビットが異なるとき、結果が 1 になります。同じなら 0 です。
1
2
3
int a = 0b_1100;
int b = 0b_1010;
Console.WriteLine(a ^ b); // 6
すべてのビットを反転します(0→1、1→0)。
1
2
int a = 0b_0000_1111; // 15
Console.WriteLine(~a); // -16
int は 32 ビットなので、0b_0000_1111 を反転すると上位ビットもすべて 1 になり、負の値になります(2 の補数表現)。
ビット列を左右にずらす演算子です。
書式:シフト演算子
1
2
3
値 << ビット数 // 左シフト
値 >> ビット数 // 右シフト(算術)
値 >>> ビット数 // 右シフト(論理)※ C# 11 以降
| 演算子 | 意味 | 効果 |
|---|---|---|
<< |
左シフト | ビットを左にずらす。右端に 0 が入る |
>> |
算術右シフト | 符号ビットを保持しながら右にずらす |
>>> |
論理右シフト | 符号に関わらず左端に 0 を入れて右にずらす(C# 11+) |
左シフトでは、各ビットが左の位置へ移動し、右端に 0 が補充されます。
1 ビット左にシフトするごとに値が 2 倍になります(x << n = x × 2ⁿ)。
1
2
3
4
int x = 1;
Console.WriteLine(x << 1); // 2 (1 × 2¹)
Console.WriteLine(x << 2); // 4 (1 × 2²)
Console.WriteLine(x << 3); // 8 (1 × 2³)
右シフトでは、各ビットが右の位置へ移動し、右端のビットは捨てられます。左端に何が入るかで 2 種類に分かれます。
算術右シフト(>>) — 符号ビット(最上位ビット)と同じ値で左端を埋めます。int などの符号付き型では >> が算術シフトになります。
正の数(符号ビット = 0)は左端に 0 が入り、値は半分になります。(以下は 8 ビット表記。実際の int は 32 ビット)
負の数(符号ビット = 1)は左端に 1 が補充され、符号が保たれます。
1
2
3
4
5
int positive = 16;
Console.WriteLine(positive >> 1); // 8(正の数は左端に 0 が入る)
int negative = -16;
Console.WriteLine(negative >> 1); // -8(負の数は左端に 1 が入り、符号が保たれる)
論理右シフト(>>>) — 符号に関わらず左端に常に 0 を入れます(C# 11 以降)。
1
2
int negative = -16;
Console.WriteLine(negative >>> 1); // 2147483640(符号ビットが 0 になる)
符号なし型(uint など)に >> を使う場合は常に論理シフトになります。
💡 ポイント: 正の数の 2 の除算には
>>を普通に使えます。負の数のビット列を符号を無視して扱いたい場合は>>>を使います。
複数の ON/OFF 状態を 1 つの整数値で管理するテクニックをビットマスクと呼びます。各ビットが 1 つのフラグに対応します。
1
2
3
4
// 各フラグを 1 ビットずつ割り当てる
const int FlagJump = 0b_0001; // ビット0: ジャンプ中
const int FlagRun = 0b_0010; // ビット1: 走り中
const int FlagCrouch = 0b_0100; // ビット2: しゃがみ中
1
2
3
4
int state = 0;
state = state | FlagJump; // ジャンプ中にする
state |= FlagRun; // 走り中も追加(|= は複合代入)
Console.WriteLine($"{state:B4}"); // 0011
特定のビットが 1 かどうかは、AND を使って 0 以外になるかで判定します。
1
2
3
4
if ((state & FlagJump) != 0)
{
Console.WriteLine("ジャンプ中");
}
1
2
state &= ~FlagJump; // ジャンプフラグだけを 0 にする
Console.WriteLine($"{state:B4}"); // 0010
~FlagJump は FlagJump のビットだけが 0 で残りが 1 のマスクになります。AND を取ることで、そのビットだけを 0 にできます。
1
state ^= FlagRun; // 走り中なら OFF に、OFF なら ON にする
[Flags] 属性付き enum — ビットマスクをより安全・わかりやすく書けます。
1
2
3
4
5
6
7
8
9
10
11
12
[Flags]
enum PlayerState
{
None = 0,
Jump = 1 << 0, // 1
Run = 1 << 1, // 2
Crouch = 1 << 2, // 4
}
var state = PlayerState.Jump | PlayerState.Run;
Console.WriteLine(state); // Jump, Run
Console.WriteLine(state.HasFlag(PlayerState.Jump)); // True
[Flags] を付けると ToString() が "Jump, Run" のように読みやすく表示されます。HasFlag でフラグの確認もできます。
💡 Unity での活用例:
LayerMaskはビットマスクで複数のレイヤーを同時に指定する仕組みです。Physics.RaycastのlayerMaskパラメータなどで使われています。
&&・||)と混同する1
2
3
4
5
6
7
8
bool a = true;
bool b = false;
// ❌ NG: & はビット演算子。bool に使えるが短絡評価されない
if (a & b) { }
// ✅ OK: && は論理 AND。左辺が false なら右辺を評価しない(短絡評価)
if (a && b) { }
条件分岐には &&・|| を使います。&・| はビット操作専用と考えると混乱しません。
== 1 と書いてしまう1
2
3
4
5
// ❌ NG: 他のフラグも立っていると 1 にならない
if ((state & FlagJump) == 1) { }
// ✅ OK: 0 以外かどうかで判定する
if ((state & FlagJump) != 0) { }
&(AND)— 両方 1 のビットだけ 1。フラグの確認・消去に使う|(OR)— どちらか 1 なら 1。フラグの追加に使う^(XOR)— 異なるビットだけ 1。フラグの切り替えに使う~(NOT)— すべてのビットを反転<<(左シフト)— 2 の乗算。x << n は x × 2ⁿ>>(算術右シフト)— 符号ビットを保持しながら右にずらす。正の数では 2 の除算>>>(論理右シフト、C# 11+)— 符号に関わらず左端に 0 を入れて右にずらす次の式の結果を 2 進数と 10 進数で答えてください。
1
2
Console.WriteLine(0b_1111 & 0b_1010);
Console.WriteLine(0b_1111 | 0b_1010);
int flags = 0b_0101 のとき、ビット 1(0b_0010)のフラグを追加した結果はいくつ(10 進数)になりますか?
1 << 4 の値はいくつですか?
0b_1111 & 0b_1010 = 0b_1010 = 10。0b_1111 | 0b_1010 = 0b_1111 = 15。
0b_0101 | 0b_0010 = 0b_0111 = 7。
16。1 × 2⁴ = 16。
反復処理 と組み合わせると、ビット列の全ビットを走査するような処理も書けるようになります。次のセクション「C# 配列と集合操作」では、複数の値をまとめて扱うデータ構造を学びます。