ビット演算って何?生徒からの質問

プログラミング

最近は生徒さんからの質問も増えてきて、先日は電池の酸化還元反応について問われました。まあ、プログラミングとは直接関係ありませんが。
今回はビット演算の話です。
コンピュータの内部では2進数が使われています。
すなわち0と1の2種類の数字を使って計算しています。
2進数で表示したときの1桁が1ビットです。8ビットは1バイトと呼ばれます。
情報の最小単位がビットで、情報の基本単位がバイトです。

通常のプログラミングではあまり行われなくなってきましたが、ビット単位で演算をすることができます。
ここではProcessingを使ってビット演算を試してみましょう。
binary()で2進数表示ができます。

■AND
両方のビットが1のときのみ結果が1になります。
例えば、aに123を、bに456を代入して a AND b の演算を行うと下記のような結果が得られます。

a 123  00000000000000000000000001111011
b 456  00000000000000000000000111001000
a AND b 00000000000000000000000001001000

■OR
いずれかのビットが1なら結果が1になります。
例えば、

a 123 00000000000000000000000001111011
b 456 00000000000000000000000111001000
a OR b 00000000000000000000000111111011

といった具合です。

■XOR
両方のビットが異なるときに1 になります。
例えば、

a 123   00000000000000000000000001111011
b 456   00000000000000000000000111001000
a XOR b  00000000000000000000000110110011

のようになります。

■左シフト
1ビット左にシフトすると2倍することができます。
例えば、nに123を代入して左シフトしてみます。

n 123    00000000000000000000000001111011
n << 1 246  00000000000000000000000011110110

nは246になりました。

■右シフト
1ビット右にシフトすると2で割ることができます。
例えば、先ほどのnを右シフトしてみます。

n 246    00000000000000000000000011110110
n >> 1 123  00000000000000000000000001111011

nは123になりました。

■1の補数
2進数の場合、補数は2つあります。1の補数と2の補数です。
元の数に1の補数を補うと2進数の最大値になります。
1の補数はビットを反転することで得られます。
例えば、mに123を代入してビット反転してみます。

m 123 00000000000000000000000001111011
~m -124 11111111111111111111111110000100

■2の補数
負の数を表現する方法です。1の補数に1を加えることで得られます。
2の補数を元の数に補うと桁が上がります。
そして、桁上がりしたビットは無視されるため0になります。
補ったら0になるということは元の数の対となる負数と言えるわけです。
最上位ビットは符号ビットと呼ばれます。
例えば、先ほどのmの2の補数を求めてみましょう。

m 123    00000000000000000000000001111011
~m+1 -123   11111111111111111111111110000101

負の数 -123 が得られました。

以上をProcessingのコードにすると以下のようになります。

 

void setup() {
  noLoop();
}

void draw() {
  // bitwise AND
  // 両方のビットが1のときのみ結果が1
  println("bitwise AND");
  int a = 123;
  int b = 456;
  println("a  ", a, binary(a));
  println("b  ", b, binary(b));
  int c = a & b;
  println("a AND b", binary(c));
  println();

  // bitwise OR
  // いずれかのビットが1なら結果が1
  println("bitwise OR");
  println("a ", a, binary(a));
  println("b ", b, binary(b));
  c = a | b;
  println("a OR b", binary(c));
  println();

  // bit XOR
  // 両方のビットが異なるときに1 
  println("bit XOR");  
  println("a  ", a, binary(a));
  println("b  ", b, binary(b));
  c = a^b;
  println("a XOR b", binary(c));
  println();

  // bit left shift
  // 2倍する
  println("bit left shift");
  int n = 123;
  println("n     ", n, binary(n));
  n = n << 1;
  println("n << 1", n, binary(n));
  println();

  // bit left shift
  // 2で割る
  println("bit right shift");
  println("n ", n, binary(n));
  n = n >> 1;
  println("n >> 1", n, binary(n));
  println();

  // bit INVERT 1の補数
  // 1の補数を補うと2進数の最大値になる
  println("bit INVERT");
  int m = 123;
  println("m   ", m, binary(m));
  m = ~m;
  println("~m ", m, binary(m));
  println();

  // bit INVERT 2の補数
  // 負の数を表現する方法
  // 2の補数を補うと桁が上がる
  // 桁上がりしたビットは無視されるため0になる
  // 補ったら0になるということは元の数の対となる負数と言える
  // 最上位ビットは符号ビット
  println("bit INVERT + 1");
  m = 123;
  println("m      ", m, binary(m));
  m = ~m + 1;
  println("~m + 1", m, binary(m));
}