Unity & C# 学習教材

ビット演算

コンピューターはすべてのデータを 0 と 1 のビットで扱います。ビット演算はそのビットを直接操作する演算です。フラグ管理・パフォーマンス最適化・低レベルなデータ操作などで使われます。

学習目標

前提知識


1. ビット演算子

4 つのビット演算子はビットを 1 本ずつ独立して処理します。

& — AND(論理積)

両方のビットが 1 のとき、結果が 1 になります。

bit3 bit2 bit1 bit0 A = B = & A & B = 1 1 0 0 = 12 1 0 1 0 = 10 1 0 0 0 = 8
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

| — OR(論理和)

どちらか一方でも 1 なら結果が 1 になります。

bit3 bit2 bit1 bit0 A = B = | A | B = 1 1 0 0 = 12 1 0 1 0 = 10 1 1 1 0 = 14
1
2
3
int a = 0b_1100;
int b = 0b_1010;
Console.WriteLine(a | b);  // 14

^ — XOR(排他的論理和)

2 つのビットが異なるとき、結果が 1 になります。同じなら 0 です。

bit3 bit2 bit1 bit0 A = B = ^ A ^ B = 1 1 0 0 = 12 1 0 1 0 = 10 0 1 1 0 = 6
1
2
3
int a = 0b_1100;
int b = 0b_1010;
Console.WriteLine(a ^ b);  // 6

~ — NOT(ビット反転)

すべてのビットを反転します(0→1、1→0)。

~(NOT)— 8 ビット表記(実際の int は 32 ビット) 入力(15) ~ 後(-16) bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0
1
2
int a = 0b_0000_1111;  // 15
Console.WriteLine(~a); // -16

int は 32 ビットなので、0b_0000_1111 を反転すると上位ビットもすべて 1 になり、負の値になります(2 の補数表現)。


2. シフト演算子

ビット列を左右にずらす演算子です。

書式:シフト演算子

1
2
3
値 << ビット数    // 左シフト
値 >> ビット数    // 右シフト(算術)
値 >>> ビット数   // 右シフト(論理)※ C# 11 以降
演算子 意味 効果
<< 左シフト ビットを左にずらす。右端に 0 が入る
>> 算術右シフト 符号ビットを保持しながら右にずらす
>>> 論理右シフト 符号に関わらず左端に 0 を入れて右にずらす(C# 11+)

左シフト — 2 の乗算

左シフトでは、各ビットが左の位置へ移動し、右端に 0 が補充されます。

← ← ← 左シフト (<<1) ← ← ← 元の値 ( = 1 ) <<1 後 ( = 2 ) bit3 bit2 bit1 bit0 0 0 0 1 消える 0 0 1 0 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(算術右シフト)— 正の数(16) 元の値( 16 ) >>1 後( 8 ) bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 0 0 0 1 0 0 0 0 消える 0 補充 (符号) 0 0 0 0 1 0 0 0

負の数(符号ビット = 1)は左端に 1 が補充され、符号が保たれます。

>>1(算術右シフト)— 負の数(-16) 元の値( -16 ) >>1 後( -8 ) bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 1 1 1 1 0 0 0 0 消える 1 補充 (符号) 1 1 1 1 1 0 0 0
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(論理右シフト)— 負の数(-16) 元の値( -16 ) >>>1 後(正の大値) bit7 bit6 bit5 bit4 bit3 bit2 bit1 bit0 1 1 1 1 0 0 0 0 消える 0 補充 (強制) 0 1 1 1 1 0 0 0
1
2
int negative = -16;
Console.WriteLine(negative >>> 1);  // 2147483640(符号ビットが 0 になる)

符号なし型(uint など)に >> を使う場合は常に論理シフトになります。

💡 ポイント: 正の数の 2 の除算には >> を普通に使えます。負の数のビット列を符号を無視して扱いたい場合は >>> を使います。


3. ビットマスク — フラグの管理

複数の 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: しゃがみ中

フラグを立てる(OR)

1
2
3
4
int state = 0;
state = state | FlagJump;  // ジャンプ中にする
state |= FlagRun;          // 走り中も追加(|= は複合代入)
Console.WriteLine($"{state:B4}");  // 0011

フラグを確認する(AND)

特定のビットが 1 かどうかは、AND を使って 0 以外になるかで判定します。

1
2
3
4
if ((state & FlagJump) != 0)
{
    Console.WriteLine("ジャンプ中");
}

フラグを下ろす(AND + NOT)

1
2
state &= ~FlagJump;  // ジャンプフラグだけを 0 にする
Console.WriteLine($"{state:B4}");  // 0010

~FlagJumpFlagJump のビットだけが 0 で残りが 1 のマスクになります。AND を取ることで、そのビットだけを 0 にできます。

フラグを切り替える(XOR)

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.RaycastlayerMask パラメータなどで使われています。


よくあるミス

論理演算子(&&||)と混同する

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) { }

まとめ


理解度チェック

  1. 次の式の結果を 2 進数と 10 進数で答えてください。

    1
    2
    
    Console.WriteLine(0b_1111 & 0b_1010);
    Console.WriteLine(0b_1111 | 0b_1010);
    
  2. int flags = 0b_0101 のとき、ビット 1(0b_0010)のフラグを追加した結果はいくつ(10 進数)になりますか?

  3. 1 << 4 の値はいくつですか?

解答を見る
  1. 0b_1111 & 0b_1010 = 0b_1010 = 100b_1111 | 0b_1010 = 0b_1111 = 15

  2. 0b_0101 | 0b_0010 = 0b_0111 = 7

  3. 161 × 2⁴ = 16


次のステップ

反復処理 と組み合わせると、ビット列の全ビットを走査するような処理も書けるようになります。次のセクション「C# 配列と集合操作」では、複数の値をまとめて扱うデータ構造を学びます。