ビット演算

ある値を 2 進数で表したとき、特定の桁を 0 にしたり 1 にしたりしたい場合があります。 例えば 2 進数で 1000 という値があって、2 桁目を 1 にすると、 1010 になります。

このようにビット毎に値を操作することを ビット演算 といいます。

ビット演算って、いつ使うの?

ビット演算は在庫管理システムとか会計システムなどのアプリケーションではあまり使うことはないかもしれません。

しかし、ハードウェアの制御など、例えばマイコンの制御などでレジスタの特定のアドレスのビットを設定する、といった状況ではよく使われます。

ビット演算で使う演算子の意味や用法は C でも C++ でも同じですが、以下の例で結果を表示するのに C++ の標準ライブラリを使っているので、C++ 言語の記事としてここに記載します。

ビット演算で使う演算子はいくつかあります。ひとつひとつみていきましょう。

NOT

NOT 演算では 0 の値をもつビットを 1 に、1 を 0 にフリップ (ひっくり返す) します。 ~ を使います。

例えば 1100 は 0011 になります。

#include <bitset>
#include <iostream>

using namespace std;

int main() {
  string s1 = "1100";
  bitset<4> b1(s1);

  cout << "b1    " << b1 << endl;

  b1 = ~b1;

  cout << "NOT~) " << b1 << endl;

  return 0;
}

実行結果:

b1    1100
NOT~) 0011

尚、上の例では C++ 標準ライブラリの bitset を使っています。これは固定サイズのビットシーケンスを表していて、ビット演算子も使えますし、 cout で出力したときに簡単にビットの内容が確認できるので使ってます。

ビット演算はここで使っている bitset だけではなく、一般に数値に対して使えます。

例えば、次の例では unsigned char 型の c を十進数の 202 で初期化しています。202 は2進数で 11001010 です。次にこれの NOT 演算を適用して結果を表示すると 53 になっています。 53 は 2 進数で 00110101 ですので、比較すると確かにビットがひっくり返っていることがわかります。

#include <iostream>
using namespace std;

int main() {
  unsigned char c = 202;
  cout << sizeof(unsigned char) << endl;
  cout << (int) c << endl;

  c = ~c;

  cout << (int) c << endl;

  return 0;
}

実行結果:

1 #unsigned char のサイズ
202 #二進数で 11001010
53 #二進数で 00110101

以下の例でも、ビット演算のところに着目してみてください。

OR

OR 演算では二つの値をとり、同じ桁のビット毎に比較しどちらかが 1 であれば 1、両方が 0 のときに 0 とします。 | を使います。

#include <bitset>
#include <iostream>

using namespace std;

int main() {
  string s1 = "1100";
  bitset<4> b1(s1);
  string s2 = "0110";
  bitset<4> b2(s2);

  cout << "b1   " << b1 << endl;
  cout << "b2   " << b2 << endl;

  b1 = b1 | b2;
  // b1 |= b2;

  cout << "OR|) " << b1 << endl;
  return 0;
}

実行結果:

b1   1100
b2   0110
OR|) 1110

元の値に対して特定の桁のビットを立てたいときには、そのビットを立てて OR 演算します。

XOR

XOR 演算では二つの値をとり、同じ桁のビット毎に比較しどちらか一方が 1 であれば 1、両方が 0 のときに 0、両方が 1 のとき 0 とします。 ^ を使います。

#include 
#include 

using namespace std;

int main() {
  string s1 = "1100";
  bitset<4> b1(s1);
  string s2 = "0110";
  bitset<4> b2(s2);

  cout << "b1    " << b1 << endl;
  cout << "b2    " << b2 << endl;

  b1 = b1 ^ b2;
  // b1 ^= b2;

  cout << "XOR^) " << b1 << endl;
  return 0;
}

実行結果:

b1    1100
b2    0110
XOR^) 1010

ビット毎に比較して 両方 1 のときには 0 にするなんて、いつ使うの?という疑問もあると思いますが、これは意外と便利です。

XOR では同じ値同士を XOR 演算すると 0 になることを使えば、ゼロクリアするのに 0 を代入するより処理が速くなります。

またフリップさせたい桁のビットを立てて XOR 演算すれば、その桁がフリップします。例えば上記の例では 11000110 を XOR 演算していますが、 中央の 2 桁に着目すると、結果は 1010 となり、確かに 1100 の中央二桁はフリップしています。

AND

AND 演算では二つの値をとり、同じ桁のビット毎に比較し両方が 1 のときのみ 1 とし、それ以外は 0 とします。 & を使います。

#include <bitset>
#include <iostream>

using namespace std;

int main() {
  string s1 = "1100";
  bitset<4> b1(s1);
  string s2 = "0110";
  bitset<4> b2(s2);

  cout << "b1    " << b1 << endl;
  cout << "b2    " << b2 << endl;

  b1 = b1 & b2;
  // b1 &= b2;

  cout << "AND&) " << b1 << endl;
  return 0;
}

実行結果:

b1    1100
b2    0110
AND&) 0100

シフト

シフト演算はビットを左または右にシフトします(ずらします)。足りない部分は 0 で埋められます。

左にシフトするには << にシフトする桁数を書きます。 同様に右にシフトするには >> を使います。

次の例では元の値が 1100 で、それを << 1 として左に 1 ビットシフトすることで、1000 となっています。

#include <bitset>
#include <iostream>

using namespace std;

int main() {
  string s1 = "1100";
  bitset<4> b1(s1);

  cout << "b1    " << b1 << endl;

  b1 = b1 << 1;
  // b1 <<= 1;

  cout << "<< 1) " << b1 << endl;
  return 0;
}

実行結果:

b1    1100
<< 1) 1000

次の例では元の値が 1100 で、それを >> 2 として右に 2 ビットシフトすることで、0011 となっています。

#include <bitset>
#include <iostream>

using namespace std;

int main() {
  string s1 = "1100";
  bitset<4> b1(s1);

  cout << "b1    " << b1 << endl;

  b1 = b1 >> 2;
  // b1 >>= 2;

  cout << ">> 2) " << b1 << endl;
  return 0;
}

実行結果:

b1    1100
>> 2) 0011

以上、ビット演算について説明しました。