タイトル: ビット演算子
SEOタイトル: Java ビット演算子の完全ガイド(& | ^ ~ << >> >>> とフラグ管理)
| この記事の要点 |
|
ビット演算子の基本
ビット演算子は整数値を 2 進数として扱い、ビット単位で計算します。Java の int は 32 bit、long は 64 bit です。
| 演算子 | 名称 | 例 | 結果 |
|---|---|---|---|
& | AND | 0b1100 & 0b1010 | 0b1000 (8) |
| | OR | 0b1100 | 0b1010 | 0b1110 (14) |
^ | XOR (排他的論理和) | 0b1100 ^ 0b1010 | 0b0110 (6) |
~ | NOT (補数) | ~0b00000000_00001100 (16bit表記) | 全ビット反転 |
<< | 左シフト | 0b0011 << 2 | 0b1100 (12) |
>> | 算術右シフト | -8 >> 1 | -4 (符号維持) |
>>> | 論理右シフト | -8 >>> 1 | 2147483644 (0 補填) |
Java サンプル
int a = 0b1100; // 12
int b = 0b1010; // 10
System.out.println(a & b); // 0b1000 = 8
System.out.println(a | b); // 0b1110 = 14
System.out.println(a ^ b); // 0b0110 = 6
System.out.println(~a); // 0xFFFF_FFF3 = -13 (2の補数)
System.out.println(a << 1); // 0b11000 = 24
System.out.println(a >> 1); // 0b00110 = 6
System.out.println(-8 >> 1); // -4 (符号維持)
System.out.println(-8 >>> 1); // 2147483644 (0 補填) ← Java 独自
// 注: byte/short の場合 int に昇格してから演算
byte b1 = (byte) 0b11110000;
int r = b1 & 0xFF; // 240 (符号なし化の定番イディオム)
用途 1: フラグ管理(ビットマスク)
複数のオン/オフ状態を 1 つの int に詰め込む古典的なテクニック。権限・状態の集合表現に使われます。
public class Permission {
public static final int READ = 1 << 0; // 0b0001 = 1
public static final int WRITE = 1 << 1; // 0b0010 = 2
public static final int EXECUTE = 1 << 2; // 0b0100 = 4
public static final int DELETE = 1 << 3; // 0b1000 = 8
}
// セット
int perms = Permission.READ | Permission.WRITE; // 0b0011
// 追加
perms |= Permission.EXECUTE; // 0b0111
// 削除
perms &= ~Permission.WRITE; // 0b0101
// 確認
boolean canRead = (perms & Permission.READ) != 0;
// トグル(あれば消す、無ければ付ける)
perms ^= Permission.DELETE;
// 立っているビット数(権限数)
int count = Integer.bitCount(perms);
Java では java.util.EnumSet がこの実装になっており、列挙型のセットを高速に扱えます。
用途 2: 高速演算
// 2 倍 / 2 で割る (符号付き整数で正の値のみ)
int x = 13;
int doubled = x << 1; // 26
int halved = x >> 1; // 6 (切り捨て)
// 4 倍
int quad = x << 2; // 52
// 2 の n 乗判定
boolean isPow2 = (x & (x - 1)) == 0 && x > 0;
// x mod 2^n (2^n - 1 とのマスクは / より高速)
int mod16 = x & 0b1111; // x % 16 と同じ
// 偶奇判定
boolean isOdd = (x & 1) == 1;
// 注: JIT 最適化で速度差は小さくなっているが、読みやすさのために
// 通常は * 2 や / 2 を使う。bit 演算は意図が明確なときのみ
用途 3: XOR の便利技
// 2 変数の値の交換(テンポラリなし)
int a = 5, b = 7;
a = a ^ b;
b = a ^ b; // = a^b^b = a (=5)
a = a ^ b; // = a^b^a = b (=7)
// → a=7, b=5 ※ 実用上は temp 変数の方が読みやすい
// 値が 0 か非 0 かのトグル
int flag = 0;
flag ^= 1; // → 1
flag ^= 1; // → 0
// 配列内で 1 つだけ重複していない値を探す
// (他は 2 個ずつあるとき、全部 XOR すると残るのが答え)
int onlyOne(int[] a) {
int r = 0;
for (int x : a) r ^= x;
return r;
}
シフト演算の詳細
// <<: 左シフト(右に 0 補填、上位ビット破棄)
// 結果は 2 の n 乗倍。オーバーフロー注意
int big = 1 << 31; // -2147483648 (符号ビットに入った)
// >>: 算術右シフト(符号維持、上位は元の符号ビットで補填)
// 負数を 2 で割るときも符号維持
-8 >> 1; // -4
-1 >> 1; // -1 (全ビット 1 のまま)
// >>>: 論理右シフト(上位を 0 で補填)★ Java/JS 独自
// 負数のビットパターンを符号なしに扱える
-1 >>> 1; // 2147483647 (0x7FFFFFFF)
// シフト量は対象型のビット数で剰余
int x = 5;
x << 33; // == x << (33 % 32) == x << 1
// long の場合は 64 で剰余
long y = 5L;
y << 65; // == y << 1
Integer の便利メソッド
Integer.bitCount(0b1011); // 3 (立っているビット数)
Integer.numberOfLeadingZeros(0b1000); // 28 (先頭の 0 個数)
Integer.numberOfTrailingZeros(0b1000); // 3 (末尾の 0 個数)
Integer.highestOneBit(0b1011); // 8 (最上位の 1 だけ残す)
Integer.lowestOneBit(0b1100); // 4 (最下位の 1 だけ残す)
Integer.reverse(0b00000000_00000000_00000000_00000001);
// → 0b10000000_00000000_00000000_00000000 = Integer.MIN_VALUE
Integer.reverseBytes(0x12345678); // 0x78563412
Integer.toBinaryString(13); // "1101"
Integer.parseInt("1101", 2); // 13
JavaScript のビット演算
JavaScript の数値は内部 64-bit 浮動小数点ですが、ビット演算では32-bit 符号付き整数に変換されます。これが落とし穴に。
// ✅ 32-bit 範囲なら期待通り
0b1100 & 0b1010; // 8
0b1100 | 0b1010; // 14
// ❌ 32-bit を超えるとビットが捨てられる
const big = 2 ** 35; // 34359738368
big | 0; // 0 (上位ビットが捨てられた)
// ✅ BigInt なら任意精度
const bigBI = 2n ** 35n;
bigBI | 0n; // 34359738368n
// 浮動小数の整数化トリック
3.7 | 0; // 3 (Math.trunc と同じ効果)
~~3.7; // 3 (~~ も同様)
PHP のビット演算
$a = 0b1100;
$b = 0b1010;
echo $a & $b; // 8
echo $a | $b; // 14
echo $a ^ $b; // 6
echo ~$a; // -13
echo $a << 1; // 24
echo $a >> 1; // 6
// PHP には >>> (論理右シフト) は無い
// 符号なしシフトをするには
echo (int)(($a & 0xFFFFFFFF) >> 1);
// 整数型の長さ
echo PHP_INT_SIZE * 8; // 64 (64-bit OS) / 32 (古い 32-bit OS)
注意点
- 論理演算子
&&/||とビット演算子&/|は別物。booleanに対しては前者を使う(短絡評価) ~は単項演算子。2 の補数表現なので~x == -x - 1- シフト量が型のビット数を超えると剰余される(Java の
1 << 32は 0 ではなく 1) - byte / short のビット演算は int に昇格 → 符号拡張で予期せぬ負数に →
& 0xFFで符号なし化 - 負の数の
%と&は等価ではない(負の数のとき結果が違う)
FAQ
Q: x & 0xFF ってよく見るけど何?
A: 下位 8 bit だけを残し、上位を 0 にするマスク。byte を符号なしの 0〜255 で扱いたいときの定番。
Q: x >> 1 と x / 2 はどちらが速い?
A: 現代の JIT/CPU では差はほぼ無し。可読性で / 2 を推奨。負数で挙動差があるので注意(-1 >> 1 = -1、-1 / 2 = 0)。
Q: ハッシュテーブルでビット演算が使われるのは?
A: 容量を 2 の n 乗にしてインデックスを hash & (n - 1) で求めるパターン。剰余より高速。Java HashMap が採用。