ビット演算
ビットの論理演算
条件式を学習した時に、真偽を論理演算で操作しました
しかし、論理演算はビットごとの演算に使うことができます
ビット演算はアルゴリズムや低レベルな処理には欠かせません
また、ビットレベルの計算に強くなるためにも必要な知識です
ビット演算には次の演算子が有効です
- & : ビットごとの論理積
- | : ビットごとの論理和
- ^ : ビットごとの排他的論理和
- ~ : 1の補数
この分野の深い内容については、多少ビットの知識が必要です
論理積はそれぞれのオペランドの各ビットを比較し、それぞれが1であれば1を
そうでなければ0という算術をします
#include <stdio.h>
int main() {
unsigned char i = 0xff;
printf("%x" , i & 0x0f);
return 0;
}
論理積の演算では、比較対象のビットが0であれば0に、1であれば比較元(左辺)の値になります
上のプログラムでは、iは16進数FFの値、つまり10進数の255の値を持っています
これに、16進数0Fの値を論理積で計算します
結果は、上位4ビットは全て0になり、下位4ビットはそのままです
この場合は0000 1111という結果(16進数のF)になります
この性質を生かせば、たとえばASCIIコードやゾーン10進数の知識さえあれば
その変換プログラムを作成することができます
次のプログラムは書式データの '5' をバイナリデータの 5 に変換しています
#include <stdio.h>
int main() {
unsigned char i = '5';
printf("%d\n" , i);
printf("%d" , i & 0x0f);
return 0;
}
特定のビットが真か偽(0か1)かを調べるのにも論理積が使われます
論理和は、ビットの一方が1であれば1という算術をします
全てを0で比較すれば元の値が、全てを1で比較すれば全ビットが1になります
#include <stdio.h>
int main() {
unsigned char i = 0x0f;
printf("%d" , i | 0xf0);
return 0;
}
このプログラムは2進数に直すと次の計算をしています
論理和は一方のオペランドのビットが1であれば1になります
結果は 1111 1111 となり、10進数の255になります
排他的論理和は、一方が1の時だけ1という結果を出します
比較したビットが同じであれば必ず0になります
特徴としては、同じ値を比較すると必ず結果は0になるということと
一方のオペランドの各ビットが1の場合は、比較もとのビットが反転するということです
#include <stdio.h>
int main() {
unsigned char i = 0x7f;
printf("%d\n" , i ^ 0xff);
printf("%d" , i ^ i);
return 0;
}
最初の演算では、変数iのビットを反転しています
16進数0x7fは2進数で 0111 1111 です
これに2進数 1111 1111 を排他的論理和で演算するとビットが反転し
1000 0000 という結果になります(10進数の128)
i ^ i は同じビットを排他的論理和で比較しています
この場合、全ての演算は0になります
1の補数とは、ビットの内容を反転させます
以前、2進数による負数の表現「サインフラグ」について少しだけ触れました
これは2の補数と呼ばれるものです
実は2の補数とは1の補数に1加算した値なのです
この事実を知っているのならば、論理演算によって負数を表すことも可能です
#include <stdio.h>
int main() {
printf("%d" , (~100) + 1);
return 0;
}
結果は -100 になります
ビット演算は実際のプログラムで多用されます
それぞれの性質を覚えておくだけでも便利でしょう
ビットシフト
ビットシフトはビットを左、または右へ移動させる演算です
ビットシフトにはビットシフト演算子を用いて行います
値 << シフト数
値 >> シフト数
<< はビットを左にシフトし
>> はビットを右にシフトします
値をいくつシフトするかは、シフト数で指定します
シフトしたことでできた空白は0で埋められます
1111 1111 >> 2 --右に2シフト--> 0011 1111
こんなのいったい何に使うんだ!?っと思うかもしれません
じつは、右にシフトするたびに2で除算した結果と同じになり
左にシフトするたびに2で乗算したことと同じになります
一般的にCPUは算術演算よりもシフト演算のほうが処理が早いというメリットがあります
(通常はコンパイラで最適化されるので変わらないそうです…)
#include <stdio.h>
int main() {
char i = 100;
printf("%d\n" , i >> 2);
printf("%d" , i << 1);
return 0;
}
もちろんデータ型のサイズを超えるシフトは切り捨てられます
また、シフトするオペランドが負数の場合は、空白は0ではなく1で埋められます
これによって、負数のシフトも意識することなく正数と同様に算術できます
#include <stdio.h>
int main() {
char i = -5;
printf("%d" , i << 2);
return 0;
}