はじめに
この記事について
「C言語の基礎を学ぼう」をテーマに、自身の知識 + α をアドベントカレンダーにまとめます。
25日間でC言語をマスターしよう - Qiita Advent Calendar 2025 - Qiita
こんな方を対象としています
-
コンピュータがプログラムをどのように動かしているか知りたい/知らない方
-
プログラミングをしてみたい方
-
C言語初心者の方
キーワード
-
2進数
-
2の補数
-
ビット演算
-
マスク
説明
2進数
プログラム内のあらゆる値は2進数で管理されています。
この記事では2進数を扱ったプログラムについて紹介します。
0b を先頭につけることで2進数を表現することができます。
1001 を10進数として出力すると下記のようになります。
#include <stdio.h>
int main(void) {
short num = 0b1001;
printf("%d", num);
return 0;
}
9
2進数は10進数と比較すると考えやすいです。
10進数の10101は $10^4+10^2+10^0=10101$ と考えることができます。
2進数も同様に考えることができ、2進数の1001は $2^3+2^0=9$ となります。
10進数と2進数と16進数について、それぞれの対応は下記のようになります。
| 10進数 | 2進数 | 16進数 |
|---|---|---|
| 0 | 0000 0000 | 00 |
| 1 | 0000 0001 | 01 |
| 2 | 0000 0010 | 02 |
| 3 | 0000 0011 | 03 |
| 4 | 0000 0100 | 04 |
| 5 | 0000 0101 | 05 |
| 6 | 0000 0110 | 06 |
| 7 | 0000 0111 | 07 |
| 8 | 0000 1000 | 08 |
| 9 | 0000 1001 | 09 |
| 10 | 0000 1010 | 0A |
| 11 | 0000 1011 | 0B |
| 12 | 0000 1100 | 0C |
| 13 | 0000 1101 | 0D |
| 14 | 0000 1110 | 0E |
| 15 | 0000 1111 | 0F |
| 16 | 0001 0000 | 10 |
| ... | ... | ... |
| 255 | 1111 1111 | FF |
2の補数
C言語の負の整数について、2進数では 2の補数表現 を使います。
ある数 $x$ について、 $x + y = 0$ となるような $y$ を、 $x$ の2の補数といいます。
例えば、2進数0101の2の補数について考えてみます。
ここは4ビットの世界だとしましょう。(ビット数が増えても同じ考え方ができます)
0101 + 1011 = 0000 となります。(5ビット目の1はあふれてしまう)
そのため、 0101 (10進数で5) の2の補数は 1011 となります。
確かめてみましょう。 short は2バイトなので 0000 0000 0000 0101 の2の補数は 1111 1111 1111 1011 ですね。
#include <stdio.h>
int main(void) {
short num = 0b1111111111111011;
printf("%d", num);
return 0;
}
-5
2の補数を簡単に求める方法があります。
- ビット反転する
- 1を足す
上記の例もそのようになっていますね。
2バイトの場合、10進数と2進数の対応は下記のようになります。
| 10進数 | 2進数 |
|---|---|
| 32767 | 0111 1111 1111 1111 |
| 32766 | 0111 1111 1111 1110 |
| ... | ... |
| 0 | 0000 0000 0000 0000 |
| -1 | 1111 1111 1111 1111 |
| -2 | 1111 1111 1111 1110 |
| -3 | 1111 1111 1111 1101 |
| ... | ... |
| -32767 | 1000 0000 0000 0001 |
| -32768 | 1000 0000 0000 0000 |
ビット演算
AND、OR、NOT、シフトについて紹介します。
AND
AND演算は下記のような演算です。日本語で「かつ」と考えるとわかりやすいです。
| A | B | AND |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
C言語では & で表します。
#include <stdio.h>
int main(void) {
short a = 0b0011, b = 0b0101;
printf("%d", a & b); // a & b は 0001 になる = 10進数で1
return 0;
}
1
OR
OR演算は下記のような演算です。日本語で「または」と考えるとわかりやすいです。
| A | B | OR |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
C言語では | で表します。
#include <stdio.h>
int main(void) {
short a = 0b0011, b = 0b0101;
printf("%d", a | b); // a | b は 0111 になる = 10進数で7
return 0;
}
7
NOT
NOT演算は下記のような演算です。否定、反転と考えるとよいですね。
| A | NOT |
|---|---|
| 0 | 1 |
| 1 | 0 |
C言語では ~ で表します。
#include <stdio.h>
int main(void) {
short a = 0b1111111111110000;
printf("%d", ~a); // ~a は 1111 になる = 10進数で15
return 0;
}
15
左シフト
左シフト演算は下記のような演算です。
| A | 左シフト |
|---|---|
| 0000 0001 | 0000 0010 |
C言語では << で表します。
#include <stdio.h>
int main(void) {
short a = 0b01;
printf("%d", a << 2); // aを2回左シフトするから100 = 10進数で4
return 0;
}
4
右シフト
右シフト演算は下記のような演算です。
| A | 論理右シフト | 算術右シフト |
|---|---|---|
| 0000 1000 | 0000 0100 | 0000 0100 |
| 1000 1000 | 0100 0100 | 1100 0100 |
シフトには論理シフト、算術シフトがあり、先頭ビットが1の場合は右シフトの挙動が異なります。環境によって論理右シフトか算術右シフトか異なることがあるようです。
C言語では >> で表します。
#include <stdio.h>
int main(void) {
short a = 0b100;
printf("%d", a >> 1); // aを1回右シフトするから010 = 10進数で2
return 0;
}
2
マスク
特定のビットを0にすることを 0マスク 、1にすることを 1マスク といいます。
マスク操作は下記のように行います。
-
0とANDすると、0でも1でも0になる = 0マスク
-
1とORすると、0でも1でも1になる = 1マスク
#include <stdio.h>
int main(void) {
short a = 0b01010011, b = 0b00001111;
short c = a & b; // aの下位4ビットを残してほかの上位ビットを0マスク
short d = a | b; // aの下位4ビットを1マスク
printf("c: %d\n", c); // 0000 0011 = 10進数で3
printf("d: %d\n", d); // 0101 1111 = 10進数で95
return 0;
}
c: 3
d: 95
0マスクは マスクする以外のビットを取り出す という目的で使われることも多いです。
練習
1. 2倍にしよう
整数aを2倍にしよう。
100の2倍は200です!
ポイント
a * 2 をすれば良い・・・のですが、ビット演算を使ってみましょう。
$a = 2 ^ n + 2 ^ m + \dots $ であるとき、
$a \times 2
= (2 ^ n + 2 ^ m + \dots ) \times 2
= (2 ^ {n + 1} + 2 ^ {m + 1} + \dots ) $ であることを利用します。
解答例
#include <stdio.h>
int main(void) {
short a = 100;
printf("%dの2倍は%dです!", a, a << 1);
return 0;
}
100の2倍は200です!
左シフト1回 = 2倍です。
2. 余りを求めよう
自然数aを8で割った余りを求めよう。
100を8で割った余りは4です!
ポイント
a % 8 をすれば良い・・・のですが、ビット演算を使ってみましょう。
$a = \dots + 2 ^ 4 + 2 ^ 3 + 2 ^ 2 + \dots $ であるとき、
$a
= \dots + 2 ^ 4 + 2 ^ 3 + 2 ^ 2 + \dots
= (\dots + 2 ^ 1 + 1) \times 2 ^ 3 + (2 ^ 2 + \dots)$ であることを利用します。
解答例
#include <stdio.h>
int main(void) {
short a = 100;
printf("%dを8で割った余りは%dです!", a, a & 0b111);
return 0;
}
100を8で割った余りは4です!
$2^3$ で割ったあまりは下位3ビットと一致します。
3. -1倍しよう
ある数aを-1倍しよう。
100の-1倍は-100です!
ポイント
a * -1 をすれば良い・・・のですが、ビット演算を使ってみましょう。
2の補数表現の変換「ビット反転して1を足す」を実装しましょう。
解答例
#include <stdio.h>
int main(void) {
short a = 100;
printf("%dの-1倍は%dです!", a, ~a + 1);
return 0;
}
100の-1倍は-100です!
おわりに
0と1を「あり」と「なし」と解釈すれば、1バイトで8つのフラグ管理をすることができます。少ないメモリ量で多くの状態を管理できて素晴らしいですね。
ビット演算はC言語ならでは感があって好きです。
参考文献
↓↓↓ はじめてプログラミングを学んだときに読んだ本です ↓↓↓
詳細(プログラミング入門 C言語)|プログラミング|情報|実教出版
