ビット演算とは?AND・OR・XOR・NOT・シフト演算を初心者向けに解説

組み込み基礎

組み込み開発を勉強していると、&、|、^、~、<<といった記号を使ったコードを目にすることがあります。

これらはビット演算と呼ばれ、数値を0と1のビット単位で操作するために使います。

特に組み込み開発では、GPIOや周辺回路を制御するレジスタの特定ビットだけを変更したり、複数の設定値を1つの数値へまとめたりするときに頻繁に登場します。

この記事では、2進数とビットの基本から、AND・OR・XOR・NOT、シフト演算、そして組み込み開発でよく使う「特定のビットだけを操作する方法」まで順番に解説します。

ビットとは?

ビット(bit)は、コンピュータが扱うデータの最小単位で、0または1のどちらかの値を持ちます。

たとえば8ビットのデータは、次のように8個の0と1で表現できます。

01001101

右端を最下位ビット(LSB:Least Significant Bit)、左端を最上位ビット(MSB:Most Significant Bit)と呼びます。

8ビットの場合、それぞれの位置は次のように表せます。

ビット位置   7  6  5  4  3  2  1  0
             ↓  ↓  ↓  ↓  ↓  ↓  ↓  ↓
             0  1  0  0  1  1  0  1

この例の01001101は、10進数では77、16進数では0x4Dです。

ビット演算の仕組み。AND・OR・XOR・NOT、左シフト・右シフト、特定のビットを1や0にする方法を示した図
ビットと2進数、基本的なビット演算、シフト演算、特定ビットを操作する方法

上図では、ビット演算で使用するAND・OR・XOR・NOTとシフト演算、さらに組み込み開発で重要になる特定ビットの操作をまとめています。ここから、それぞれの仕組みを詳しく見ていきましょう。

ビット演算とは?

ビット演算とは、整数を構成しているそれぞれのビットに対して行う演算です。

C/C++では、主に次の演算子を使用します。

演算演算子意味
AND&両方が1なら1
OR|どちらかが1なら1
XOR^異なっていれば1
NOT~ビットを反転
左シフト<<ビットを左へ移動
右シフト>>ビットを右へ移動

AND演算とは?

AND演算は、対応する2つのビットが両方とも1の場合だけ1になります。

ABA & B
000
010
100
111

たとえば次の2つの値をAND演算してみます。

  11001100
& 10101010
------------
  10001000

AND演算は、特定のビットが1かどうかを調べたり、不要なビットを0にしたりするときによく使います。

OR演算とは?

OR演算は、対応するビットのどちらか一方でも1なら1になります。

ABA | B
000
011
101
111

たとえば次のようになります。

  11000000
| 00001000
------------
  11001000

組み込み開発では、特定のビットだけを1にするときによく使用します。

XOR演算とは?

XOR(排他的論理和)は、2つのビットが異なる場合に1になります。

ABA ^ B
000
011
101
110

XORには、1とのXORを取ると元のビットが反転するという特徴があります。

0 ^ 1 = 1
1 ^ 1 = 0

この性質を利用すると、特定のビットだけを反転できます。

NOT演算とは?

NOT演算は、各ビットの0と1を反転します。

0 → 1
1 → 0

C/C++では~を使います。

uint8_t value = 0b00001111;

value = ~value;

8ビットのvalueとして見ると、結果は次のようになります。

00001111
   ↓ NOT
11110000

実際のC/C++では整数型への変換なども関係するため、ビット幅を意識して扱うことが重要です。

シフト演算とは?

シフト演算は、ビット列を左または右へ移動させる演算です。

左シフトには<<、右シフトには>>を使用します。

左シフト

00000001 << 1
      ↓
00000010

1 << 1は2、1 << 2は4、1 << 3は8になります。

1 << 0 = 00000001 = 1
1 << 1 = 00000010 = 2
1 << 2 = 00000100 = 4
1 << 3 = 00001000 = 8

この「1を指定した位置まで移動する」という操作が、後ほど説明する特定ビットの操作で非常に重要になります。

右シフト

00001000 >> 1
      ↓
00000100

符号なし整数では、右へ1ビットシフトすると値は2で割った結果に対応します。ただし、シフトによる乗除算として考える場合は型の幅、符号、あふれなどに注意が必要です。

1 << 3 は何を意味している?

組み込みのコードでは、次のような記述をよく見かけます。

1 << 3

これは「1を左へ3ビット移動する」という意味です。

00000001
   ↓ 3ビット左へ
00001000

つまり、ビット3だけが1になった値を作っています。

このような値を、特定ビットを操作するためのビットマスクとして利用できます。

特定のビットを1にする

特定のビットだけを1にしたい場合は、OR演算とシフト演算を組み合わせます。

value |= (1 << 3);

たとえば元の値が次の状態だったとします。

value
00000000

1 << 3
00001000

OR
--------
00001000

これで、ほかのビットを維持したままビット3だけを1にできます。

特定のビットを0にする

特定のビットだけを0にするときは、ANDとNOTを組み合わせます。

value &= ~(1 << 3);

まず1 << 3でビット3だけが1の値を作ります。

00001000

NOTで反転すると、ビット3だけが0になります。

11110111

これと元の値をAND演算することで、ほかのビットを維持したままビット3だけを0にできます。

特定のビットを反転する

特定のビットを0→1、または1→0へ反転したい場合はXORを使えます。

value ^= (1 << 3);

ビット3が0なら1へ、1なら0へ切り替わります。

特定のビットが1か確認する

特定のビットの状態を確認するときにはAND演算を利用できます。

if (value & (1 << 3)) {
  // ビット3が1
}

valueとビット3だけが1のマスクをAND演算し、結果が0以外ならビット3が1だと判断できます。

ビットマスクとは?

必要なビットだけを取り出したり変更したりするために使う値を、ビットマスク(bit mask)と呼びます。

たとえばビット3を操作するマスクは次のように作れます。

const uint8_t BIT3_MASK = (1U << 3);

これを使えば、コードの意味も分かりやすくなります。

value |= BIT3_MASK;   // ビット3を1
value &= ~BIT3_MASK;  // ビット3を0
value ^= BIT3_MASK;   // ビット3を反転

なぜ組み込み開発でビット演算を使うの?

組み込み開発でビット演算が重要なのは、マイコンの設定情報が1つのレジスタの各ビットに割り当てられていることが多いためです。

たとえば架空の8ビットレジスタを考えてみます。

bit 7  6  5  4  3  2  1  0
    ┌──┬──┬──┬──┬──┬──┬──┬──┐
    │  │  │  │  │EN│  │  │  │
    └──┴──┴──┴──┴──┴──┴──┴──┘

もしビット3が「機能を有効にするENビット」だった場合、その機能だけを有効にするには次のような操作ができます。

REGISTER |= (1U << 3);

ほかの設定ビットを変更せず、必要なビットだけを1にできるのがポイントです。

GPIOや周辺回路の設定にも使われる

ビット演算は、次のようなマイコン内部の設定でよく登場します。

  • GPIOの設定
  • 割り込みの有効・無効
  • タイマーの設定
  • UART・SPI・I2Cなどの周辺回路設定
  • ステータスフラグの確認
  • エラーフラグの管理
  • 複数の設定値を1つの変数へ格納する処理

ArduinoのdigitalWrite()などを使っているとビット演算を直接書かない場合もありますが、その下ではレジスタやビット単位の制御が行われています。

KUMITATE-C3でビット演算を試してみる

KUMITATE-C3でも、Arduino環境を使ってビット演算そのものを簡単に確認できます。

次のプログラムでは、8ビット変数の特定ビットを操作し、結果をシリアルモニターへ2進数で表示します。

uint8_t value = 0;

void setup() {

  Serial.begin(115200);

  value |= (1U << 3);

  Serial.print("bit3 ON : ");
  Serial.println(value, BIN);

  value |= (1U << 0);

  Serial.print("bit0 ON : ");
  Serial.println(value, BIN);

  value &= ~(1U << 3);

  Serial.print("bit3 OFF: ");
  Serial.println(value, BIN);

  value ^= (1U << 0);

  Serial.print("bit0 XOR: ");
  Serial.println(value, BIN);
}

void loop() {
}

変化を8ビット表記で考えると、次のようになります。

初期値
00000000

bit3 ON
00001000

bit0 ON
00001001

bit3 OFF
00000001

bit0 XOR
00000000

このように1ビットずつ変化を追ってみると、ビット演算の動作を理解しやすくなります。

論理演算子とビット演算子を混同しない

C/C++では、ビット演算子と似た記号の論理演算子があります。

種類ANDORNOT
ビット演算&|~
論理演算&&||!

&&や||は条件式などの真偽値を扱うために使い、&や|は整数の各ビットに対して演算します。

見た目が似ていますが役割は異なるので注意しましょう。

16進数も一緒に覚えると便利

組み込み開発では、2進数だけでなく16進数も頻繁に使用します。

16進数1桁は4ビットに対応するため、長いビット列を短く表現できます。

2進数   1111 0000
16進数  0xF0

データシートやレジスタの説明では16進数が頻繁に登場するため、ビット演算と一緒に2進数・16進数の対応を覚えておくと便利です。

まとめ

ビット演算は、整数を構成する0と1をビット単位で操作する方法です。

  • ANDは両方のビットが1の場合に1になる
  • ORはどちらかのビットが1なら1になる
  • XORは2つのビットが異なる場合に1になる
  • NOTは0と1を反転する
  • <<と>>でビットを左右へシフトできる
  • 1U << nで特定位置だけが1のマスクを作れる
  • ORで特定ビットを1にできる
  • ANDとNOTで特定ビットを0にできる
  • XORで特定ビットを反転できる
  • ANDで特定ビットの状態を確認できる

ビット演算を理解すると、マイコンのデータシートに登場するレジスタ設定が一気に読みやすくなります。

次は、今回学んだビット演算が実際に使われる「レジスタ」について学んでいきましょう。

KUMITATE

読むだけでなく、
実際に動かして学びませんか?

クミタテは、組み込み開発や電子工作を 実践しながら学べる学習プラットフォームです。 ESP32を使ったプログラミングから、電子回路、センサー、 通信、基板設計まで、自分のペースで学習できます。

  • 無料で学べる実践的な教材を掲載
  • すぐに試せるサンプルプログラム付き
  • Googleアカウントですぐに登録可能
クミタテに無料登録する
組み込み基礎
スポンサーリンク