プログラムでは、数値をビット(0と1)で表しています。
C言語では、このビットを直接操作できる「ビット演算」と「シフト演算」が用意されています。
今回は、そのビット演算とシフト演算の基本的な使い方を説明します。
ビット演算とは
ビット演算とは、0と1の並び(ビットの集まり)を直接操作する方法 のことです。
1つ1つのビットに対して、「同じ位置のビットどうしで計算する」ようなイメージです。
例えば、次のように2つの数があったとします。
a = 1100
b = 1010この2つのビットを、1列ずつ比較して結果を作るのがビット演算です。
例えば、次のような2つの数があるとします。
a = 1100(=12)
b = 1010(=10)この2つの数を「ビットごと(1列ずつ)」に比べて、0と1の組み合わせに応じて計算していくのがビット演算です。
ふつうの演算(+、−、×、÷)は「数」そのものを計算しますが、ビット演算は「0と1の並び」を1つずつ比べて処理します。
ビット演算には、AND(&)、OR(|)、XOR(^)、NOT(~)を使用します。
| 演算子 | 名称 | 説明 | 例 |
& | AND(論理積) | 両方が1なら1 | 1 & 1 = 1 |
| | OR(論理和) | どちらかが1なら1 | 1 | 0 = 1 |
^ | XOR(排他的論理和) | 違っていたら1 | 1 ^ 1 = 0 1 ^ 0 = 1 |
~ | NOT(否定) | 反転 | ~1 = 0 |
それぞれのビット演算子の例です。
&演算子(AND)の例
両方が1のときだけ1になります。
unsigned char a = 0b1100; // 12
unsigned char b = 0b1010; // 10
unsigned char c = a & b; // 1000 → 8| a | b | 結果(c) |
| 1 | 1 | 1 |
| 1 | 0 | 0 |
| 0 | 1 | 0 |
| 0 | 0 | 0 |
つまり、「共通して1のところ」だけを取り出します。
このため、特定のビットを取り出すときに使われます。
|演算子(OR)の例
どちらかが1なら、結果も1になります。
unsigned char a = 0b1100; // 12
unsigned char b = 0b1010; // 10
unsigned char c = a | b; // 1110(=14)| a | b | 結果(c) |
| 1 | 1 | 1 |
| 1 | 0 | 1 |
| 0 | 1 | 1 |
| 0 | 0 | 0 |
「どちらかがONならONになる」ようなイメージです。
複数の条件をまとめてONにするときに使います。
^演算子(XOR)の例
同じなら0、ちがっていたら1になります。
unsigned char a = 0b1100; // 12
unsigned char b = 0b1010; // 10
unsigned char c = a ^ b; // 0110(=6)| a | b | 結果(c) |
| 1 | 1 | 0 |
| 1 | 0 | 1 |
| 0 | 1 | 1 |
| 0 | 0 | 0 |
「スイッチを反転させる」ようなときに使えます。
~演算子(NOT)の例
すべてのビットを反転させます。
unsigned char a = 0b00001111;
unsigned char b = ~a; // 111100000は1に、1は0に入れ替わります。
シフト演算とは
シフト演算は、ビットを左右にずらす演算です。
0と1の並びをずらすことで、数の大きさが変わります。
| 名前 | 演算子 | 説明 |
| 左シフト | << | 左にずらす(2倍ずつ大きくなる) |
| 右シフト | >> | 右にずらす(1/2ずつ小さくなる) |
<< 演算子(左シフト)の例
左にビットをずらすと、空いた右側のビットには0が入ります。
unsigned char a = 0b00000010; // 2
unsigned char b = a << 1; // 0000 0100 → 41ビット左にずらすと「2倍」になります。
つまり、「左にn回ずらすと 2のn乗倍」になります。
| 式 | 結果 |
| 1 << 1 | 2(0b10) |
| 1 << 2 | 4(0b100) |
| 1 << 3 | 8(0b1000) |
>> 演算子(右シフト)の例
右にビットをずらすと、左側に何が入るかがポイントです。
unsigned char a = 0b00001000; // 8
unsigned char b = a >> 2; // 0000 0010 → 2右に1つ動かすと1/2になります。
ただし、「左の空いたビット」に入る値は型によって異なります。
| 種類 | 空いたビットに入る値 | 呼び方 |
| unsigned(符号なし)の型 | 0が入る | 論理シフト |
| signed(符号あり)の型 | 符号ビット(最上位ビット)の値が入る | 算術シフト |
符号なし(unsigned)の右シフトの例
unsigned char a = 0b11111000; // 248
unsigned char b = a >> 1; // 0111 1100 → 124unsignedが付いた型の右シフトは、空いた左のビットには常に0が入ります。
これが、論理シフトです。
符号あり(signed)の右シフトの例
符号あり(signed)の場合は、符号ビット(最上位ビット)の値が入るため、このビットが、0 なら、シフトした後に空いたビットには、0 が入ります。
符号ビット(最上位ビット)が 0 なら、シフトした後に空いたビットには、1 が入ります。
つまり、ゼロとプラスの値の場合は、シフトした後に空いたビットには、0 が入り、マイナスの値の場合は、シフトした後に空いたビットには、1 が入ります。
符号ビット(最上位ビット)とは、一番左側にあるビットのことです。
例えば、-8 は、8ビットで次のようになります。
1111 1000
これを右に1ビットシフトすると、
1111 1000 >> 1 → 1111 1100(-4)左のビット(最上位ビット)が「1(符号ビット)」で埋まります。
これを 算術シフト といいます。 負のまま半分にする動きです。
符号あり(signed)でも、負の値(マイナスの値)でない場合(プラスの値)は、符号あり(最上位ビット)は 0 です。
このため、符号ありでも、プラスの値の場合は、右シフトしても、左のビット(最上位ビット)は「0(符号ビット)」で埋まります。
符号ありの 8 は、8ビットで次のようになります。
0000 1000 >> 1 → 0000 0100(4)2の補数とは
-8 が 1111 1000 となっていることに疑問が出てきました。
なぜ、-8 が2進数では、この値になるのでしょうか。
それは、マイナスの値を 2の補数 という方法で表すからです。
これは、マイナスの値もビットの並びだけで表す必要があるためです。
例えば、 5 と -5 を8ビットで表すと、
0000 0101 (5)
1111 1011 (-5)-5 は、5 を反転して(0と1を入れ替えて)+1 していることが分かります。
つまり、2の補数とは、ビットを反転して+1しているということです。
8ビットは、256通りの組み合わせしかありません。0000 0000 ~ 1111 1111
の256通りの組み合わせです。
もしこれを、「0~255」として使うと、マイナスの値が入りません。
そこで、半分をマイナスの値として使うようにします。
0000 0000 ~ 0111 1111: 0~1271000 0000 ~ 1111 1111: -128~-1
どうすれば「足し算」でマイナスを表せるか
コンピュータは、引き算ができません。
(正確には「引き算用の回路を作らずに、足し算で引き算をしたい」)
このため、5 - 5 = 0 は、5と何を足したら 0 となるかで表そうと考えます。
すなわち、5 + (-5) = 0 と考えます。
8ビットで、5と-5を表わすと、
5 :0000 1001
-5:1111 0111
これを足すと、
0000 1001 (5)
+1111 0111 (-5)
--------------
= 1 0000 0000
となります。一番左端の1は、桁があふれているため、切り捨てられます。
このため、 0000 0000 となり、結果、0 となります。
このように、2の補数を使用することにより、足し算の仕組みだけで、ひき算ができます。
つまり、マイナスの数もプラスの数も、同じ足し算回路で処理できる のです。
そのため、CPUの構造をシンプルにできて、高速に計算できるようになります。
まとめ
- コンピュータの中では、すべてのデータは 0と1(ビット) の並びで表されている
- ビット演算は、数を作る0と1を「1つずつ計算」する演算
- 主なビット演算には次の4つがある
- AND(&):両方が1のときだけ1になる
- OR(|):どちらかが1なら1になる
- XOR(^):異なっていれば1、同じなら0になる
- NOT(~):0と1を入れ替える(反転する)
- シフト演算は、ビットを左右にずらす演算
- 左シフト(<<):右の空いたビットに0が入り、2倍ずつ大きくなる
- 右シフト(>>):左の空いたビットには
- 符号なし(unsigned)なら0が入る(論理シフト)
- 符号あり(signed)なら符号ビットが入る(算術シフト)
- 負の数は 2の補数 という仕組みで表されており、「ビットを反転して+1」することで作る