はじめに
最近、ビット演算について学ぶ機会がありました。最初は難しそうに思えたビット演算ですが、実はとてもシンプルで強力なツールであることがわかりました。ここではビット演算について、仕組みや利用シーンについて勉強したことをシェアしていきます。
ビット演算とは?
ビット演算とは、数値を ビット単位 で操作する演算方法です。数値はコンピュータ内部で 2進数(0と1)で表されているため、ビットごとに計算を行うことで、効率的な処理が可能です。
各桁ごとに計算される
ビット演算は、各ビット(桁)ごとに演算が行われます。この動作は、10進数の演算において 同じ位同士を計算するのをイメージするとわかりやすいです。10進数での加算では、1の位、10の位、100の位と順に計算が行われますが、ビット演算では2進数の各ビットが順番に計算されます。
例えば、2進数の 5
(0101)と 3
(0011)を AND演算(&
)する場合、対応するビット同士を比較し、両方が1なら1、それ以外は0となります。
0101 (5)
& 0011 (3)
----
0001 (1)
ビット演算の基本的な操作
ビット演算にはいくつかの基本的な演算子があります。それぞれ、2進数の各ビットごとに計算が行われます。
AND(&)
両方のビットが 1 である場合、結果も 1 になります。それ以外は 0 です。
例:5 & 3 → 0101 & 0011 = 0001 → 結果は 1
0101 (5)
& 0011 (3)
----
0001 (1)
OR(|)
どちらか一方のビットが 1 であれば、結果は 1 になります。
例:5 | 3 → 0101 | 0011 = 0111 → 結果は 7
0101 (5)
| 0011 (3)
----
0111 (7)
XOR(^)
ビットが異なる場合に 1 になります。同じ場合は 0 です。
例:5 ^ 3 → 0101 ^ 0011 = 0110 → 結果は 6
0101 (5)
^ 0011 (3)
----
0110 (6)
左シフト(<<)
ビットを左にシフトし、右に 0 を埋めます。これは数値を2倍にする操作と等価です。
例:5 << 1 → 0101 << 1 = 1010 → 結果は 10
5 << 1
=> 0101 << 1 //0101の各桁の値を1つ左にずらす
=> 1010 (10)
右シフト(>>)
ビットを右にシフトし、符号ビットで埋めます。これは数値を2で割る操作と同じです(小数点以下は切り捨て)。
例:5 >> 1 → 0101 >> 1 = 0010 → 結果は 2
5 >> 1
=> 0101 >> 1 //0101の各桁の値を1つ右にずらす
=> 0010 (2)
具体的なビット演算の利用シーン
1. 偶数・奇数の判定
ビット演算を使うと、ある数値が 偶数か奇数か を簡単に判定できます。二進数では、偶数は最下位ビットが 0
、奇数は 1
になっているため、最下位ビットを確認するだけで判定が可能です。
具体例
function isEven(num: number): boolean {
return (num & 1) === 0;
}
console.log(isEven(4)); // true(偶数)
console.log(isEven(5)); // false(奇数)
-
偶数の最下位ビットは常に
0
で、奇数の最下位ビットは1
です -
num & 1
で最下位ビットが0
か1
かを確認します
2. 2で割る、掛ける操作
ビットシフト演算を使って、数値を 2で割る、掛ける 操作を高速に行うことができます。右に1ビットシフトすると、数値が2で割られ、左に1ビットシフトすると、数値が2で掛けられ、小数点以下は切り捨てられます。
右に1ビットシフトして 2で割る
function divideByTwo(num: number): number {
return num >> 1;
}
console.log(divideByTwo(4)); // 2
console.log(divideByTwo(5)); // 2(小数点以下は切り捨て)
num >> 1
: 数値を右に1ビットシフトすると、2で割る操作が行われます。小数点以下は切り捨てられます
左に1ビットシフトして 2で掛ける 操作
function multiplyByTwo(num: number): number {
return num << 1;
}
console.log(multiplyByTwo(4)); // 8(4 × 2 = 8)
console.log(multiplyByTwo(7)); // 14(7 × 2 = 14)
num << 1
: 数値を左に1ビットシフトすると、2で掛ける操作が行われます。
- 右にビットシフト(>>): 数値を2で割る操作と同じです。ビットを1つ右に移動することで、数値が半分になります(小数点以下は切り捨てられます)
- 左にビットシフト(<<): 数値を2で掛ける操作と同じです。ビットを1つ左に移動することで、数値が2倍になります
その他の利用シーン
ビット演算は、奇数・偶数判定や2の倍数の計算以外にもさまざまな場面で効率的に利用できます。
1. ビットマスクを使った特定のビットの操作
ビットマスクを用いることで、特定のビットを確認・設定・リセットすることが可能です。フラグ管理や権限のチェックなどで利用されます。
利用シーン例: フラグのON/OFF、権限管理、特定ビットの確認。
2. フラグ管理
1つの数値で複数のフラグ(ON/OFF状態)を管理し、効率的に状態を制御できます。ゲーム開発や設定の保存で利用されます。
利用シーン例: 複数の設定を1つの変数で管理、状態フラグの制御。
3. ビットカウント(1のビット数を数える)
ある数値に含まれる1ビットの数(ハミング重量)を数えることで、データ処理や圧縮に活用されます。
利用シーン例: エラーチェック、圧縮アルゴリズム、データ解析。
4. ビット回転(ローテーション)
ビットを回転させる操作は、暗号化やハッシュ関数で使われ、データの順序を効率的に操作できます。
利用シーン例: 暗号化アルゴリズム、ハッシュ関数、データシフト。
5. 符号反転(負数の計算)
ビット反転と加算を使って、効率的に符号を反転させることが可能です。負の数の計算に活用されます。
利用シーン例: 数値の符号反転、負の数計算。
まとめ
ちょっととっつきにくいビット演算ですが、10進数と同じようなことが2進数で行われていることがわかれば、理解しやすいと思います。ビット演算は、各ビット(桁)ごとに計算が行われるため、10進数での桁ごとの計算と同じような感覚で理解できます。偶数・奇数の判定や2の倍数の操作に優れている理由は、各ビットが2進数の桁に対応しているためです。
10進数に変換しない分高速に処理できるため、処理速度を突き詰めて行くと、ビット演算の理解は大切だなと勉強してみて思いました。誰かの参考になれば幸いです。