ビット演算とシフト演算を超かみ砕いて、図とJavaの実例で丁寧に説明します。初心者向けに「何をしているか」「なぜ使うか」「よくある使い方(フラグ管理など)」までカバー。最後に練習問題(解答つき)も付けます。
ビット演算とシフト演算 — 基本のイメージ
コンピュータは数を0 と 1 の並び(ビット)で扱っています。
ビット演算はその「0/1 の並び」に対して直接操作する道具です。
シフト演算は「ビット列を左右にズラす」道具です。
1. 二進数のイメージ(まずはこれが基本)
10進数 5 を 4ビットで表すと:5 → 0101(左が上位ビット)
10進数 3 → 0011。
これを頭の中でイメージできると、ビット演算が見えてきます。
2. ビット演算(AND, OR, XOR, NOT)
& ビットAND(両方1なら1)
例:5 & 3
5 = 0101
3 = 0011
& = 0001 -> 1
意味:共通して立っているビットだけ残す。マスクに使う。
| ビットOR(どちらか1なら1)
例:5 | 3
5 = 0101
3 = 0011
| = 0111 -> 7
意味:どちらかが1なら結果は1。複数フラグをまとめるのに便利。
^ ビットXOR(排他的論理和:片方だけ1のとき1)
例:5 ^ 3
5 = 0101
3 = 0011
^ = 0110 -> 6
意味:違う部分だけ残す。ビットの切り替え(toggle)などに使える。
~ ビットNOT(ビットを反転)
例:~5
表現上、Java の int は 32 ビットで処理されるため ~5 = -6 になります(詳細は下で説明)。
直感:0101 → 1010(ただし32ビットで考える)。
補足(簡単な覚え方):
&は「共通だけ残す」、|は「どちらかあれば残す」、^は「違うところだけ残す」、~は「全部ひっくり返す」。
3. シフト演算(ビットを左右にズラす)
イメージ:ビット列を「ポン」と左/右に移動させる。空いたところは 0 か符号ビットで埋められます。
<< 左シフト
例:5 << 1
5 = 0000 0101
<<1 -> 0000 1010 -> 10
左に1ビット移動=×2(整数なら概ね2倍になる)。<< n は ×(2^n)。
>> 右シフト(符号あり)
例:5 >> 1 → 2 (小数切り捨ての除算っぽい)
負の数は符号ビット(左端)で埋めるため符号維持します。例:-8 >> 2 → -2。
>>> 右シフト(符号なし、論理右シフト)
左端は常に0で埋める(負数でも0を入れるので大きな正の値になることがある)。Javaで負の値に使うと注意。
4. 負の数と ~、シフトの注意(初心者が混乱しやすい点)
Javaの整数は2の補数表現(two’s complement)で保持されます。意味:
- マイナスは単にビット列をそのまま反転して+1した表現。
- だから
~x(ビット反転)は簡単に言うと~x = -x - 1という関係になります。例:~5 = -6。
また >> は符号を保つので負の数を右にシフトすると負の値のまま(数学的には floor に近い結果)になりますが、>>> は符号を無視して0で埋めるため結果が大きく変わり得ます(普通は符号付きが欲しい場面が多い)。
5. よくある実用パターン(フラグ管理・マスク)
ビット演算が最もよく使われる場面の1つが「複数の ON/OFF をひとつの整数で管理する」こと(例:設定のフラグ)。
final int FLAG_A = 1 << 0; // 0001 -> 1
final int FLAG_B = 1 << 1; // 0010 -> 2
final int FLAG_C = 1 << 2; // 0100 -> 4
int flags = 0;
// フラグを立てる(ON)
flags |= FLAG_A; // flags = flags | FLAG_A
// フラグを消す(OFF)
flags &= ~FLAG_B; // flags = flags & (not FLAG_B)
// フラグが立っているか確認
boolean hasA = (flags & FLAG_A) != 0;
// フラグを切り替える(toggle)
flags ^= FLAG_C;
Javaこの使い方は設定や状態を効率よく扱うときに頻出します。
6. 実際の Java サンプル(実行して試せる)
public class BitExamples {
public static void main(String[] args) {
int a = 5; // 0101
int b = 3; // 0011
System.out.println("a & b = " + (a & b)); // 1
System.out.println("a | b = " + (a | b)); // 7
System.out.println("a ^ b = " + (a ^ b)); // 6
System.out.println("~a = " + (~a)); // -6 (32bit の反転のため)
System.out.println("a << 1 = " + (a << 1)); // 10
System.out.println("a >> 1 = " + (a >> 1)); // 2
System.out.println("a >>> 1 = " + (a >>> 1)); // 2 (正数では >> と >>> は同じ)
int neg = -1;
System.out.println("-1 >>> 1 = " + (neg >>> 1)); // 2147483647(大きな正数になる)
}
}
Java上のコードをエディタで動かして、各行の結果を確かめてください。実際に実行してみるとビット操作の理解がぐっと深まります。
7. 視覚的な図(小さなイメージ)
例:5 << 2
5 = 0000 0101
<<2 -> 0001 0100 = 20
例:-1 >>> 1(符号なし右シフト)
-1 (32bit) = 1111 ... 1111
>>>1 -> = 0111 ... 1111 -> 2147483647
8. 練習問題(初心者向け) — 解答付き
問題1. 6 & 3 の答えは?(10進で)
解答1. 6 = 0110, 3 = 0011 → 0110 & 0011 = 0010 → 2
問題2. 6 | 3 の答えは?
解答2. 0110 | 0011 = 0111 → 7
問題3. 6 ^ 3 の答えは?
解答3. 0110 ^ 0011 = 0101 → 5
問題4. 8 >> 2 の答えは?(整数除算に近い)
解答4. 8 / 2^2 = 8 / 4 = 2 → 2
問題5. 5 << 3 の答えは?
解答5. 5 * 2^3 = 5 * 8 = 40 → 40
問題6(注意問題). Javaで ~0 は何?(ヒント:32ビットの反転)
解答6. 0 は 000...0000 → ~0 は 111...1111(32ビット)→ これは -1 を表します。→ -1
9. 初心者のためのコツ・落とし穴
- 小さい例で二進数を書いて確認する習慣をつけると分かりやすい。
- 負の数や
>>>を使うときは結果が大きく変わるので注意。 - 「倍にする/割る」感覚でシフトが使えるが、浮動小数点や丸めの影響は違うので整数専用のテクニックと考える。
- 可読性も重要。ビット演算は速いが、コメントを付けるなどして意味を明確にしておく。
もっと踏み込んだ例が欲しい?たとえば:
- 「フラグ管理を使ったメニューのON/OFFを作るステップ実装」
- 「2の補数を図で丁寧に説明(負の数がどう表現されるか)」
- 「練習問題を増やして模擬テスト(採点付き)」
どれを先にやろうか?それとも今すぐ上の Java サンプルで実行結果を一緒に読み解きますか? 😄
