C言語の基本|2進数・8進数・16進数の基本

同じ値でも書き方はひとつではない。進数のつながりを知って、コンピューターの数字を読み解こう。

私たちが普段の生活で使用している数は、0から9までの数字を使う10進数です。

一方、C言語の学習を進めると、0と1だけで表す2進数、0から7までを使う8進数、0から9とAからFまでを使う16進数が登場します。

最初は、同じ数字なのに表し方がいくつもあるため、少し難しく感じるかもしれません。

しかし、進数が変わっても、値そのものが別のものになるわけではありません。同じ値を、異なる数字と桁の仕組みで表現しているだけです。

たとえば、10進数の123は、次のように表せます。

10進数:123
2進数:1111011
8進数:173
16進数:7B

どれも表し方が違うだけで、値は同じ123です。

C言語では、特に次のような場面で進数の知識が役立ちます。

  • コンピューター内部の0と1を理解する
  • メモリに保存された値を読む
  • ビット演算を理解する
  • フラグの組み合わせを確認する
  • 文字コードを確認する
  • ハードウェアや機器を制御する
  • 16進数で表示されたデータを読む
  • 8進整数定数の書き方を理解する

このパートでは、進数の基本、桁の重み、10進数からの変換、C言語での書き方、printfによる表示方法を順番に確認します。

数字が苦手でも、桁ごとの仕組みを一つずつ見ていけば大丈夫です。

進数とは数を表すための仕組み

進数とは、何種類の数字を使って値を表すかを決める仕組みです。

10進数では、0から9までの10種類の数字を使います。

0 1 2 3 4 5 6 7 8 9

9の次へ進むと桁が上がり、10になります。

2進数では、0と1の2種類だけを使います。

0 1

1の次へ進むと桁が上がり、10になります。ただし、これは2進数の10なので、10進数では2を表します。

8進数では、0から7までの8種類の数字を使います。

0 1 2 3 4 5 6 7

7の次は10になり、この8進数の10は、10進数では8を表します。

16進数では、0から9に加えてAからFまでを使います。

0 1 2 3 4 5 6 7 8 9 A B C D E F

Fの次は10になり、この16進数の10は、10進数では16を表します。

進数ごとに使用する数字が異なる

進数使用する数字桁が上がるタイミング主な用途
2進数0、12ごとコンピューター内部の基本表現
8進数0から78ごと2進数を3桁ずつまとめる
10進数0から910ごと日常生活で使う一般的な表記
16進数0から9、AからF16ごと2進数を4桁ずつまとめる

進数によって、1桁で表せる範囲が異なります。

進数1桁で表せる最小値1桁で表せる最大値
2進数01
8進数07
10進数09
16進数0F

桁にはそれぞれ重みがある

10進数の123は、桁ごとに分けると次のようになります。

1 × 102 + 2 × 101 + 3 × 100

計算すると、次の値になります。

1 × 100 + 2 × 10 + 3 × 1
= 100 + 20 + 3
= 123

右端から順番に、1、10、100という重みを持っています。

2進数では、右端から順番に1、2、4、8、16と重みが増えます。

8進数では、1、8、64、512と増えます。

16進数では、1、16、256、4096と増えます。

2進数の重み8進数の重み10進数の重み16進数の重み
右から1桁目1111
右から2桁目281016
右から3桁目464100256
右から4桁目851210004096
右から5桁目1640961000065536

進数を理解するときは、各桁の数字だけでなく、その桁が持つ重みを考えることが大切です。

図1:進数によって桁の重みが変わる

この図から分かること

同じ桁数でも、進数が違えば、それぞれの桁が持つ重みも変わります。

2進数では右へ1桁移動するごとに重みが2分の1になり、左へ1桁移動するごとに2倍になります。

2進数101は、4の位、2の位、1の位に分けて計算できます。

1 × 4 + 0 × 2 + 1 × 1
= 5

2進数は0と1で数を表す

2進数は、0と1だけを使って値を表す方法です。

コンピューターの電子回路では、電気が流れている状態と流れていない状態など、2つの状態を区別して情報を扱います。

この2つの状態を、数値の1と0に対応させて考えられるため、コンピューター内部では2進数が基本になります。

一般的には、次のようなイメージで説明されます。

2進数状態のイメージ
0OFF、電気がない、偽
1ON、電気がある、真

実際のコンピューター内部はさらに複雑ですが、最初は0と1の2状態でデータを表すと理解しておけば大丈夫です。

2進数の桁の重み

2進数は、右端から順番に次の重みを持ちます。

1、2、4、8、16、32、64、128

これらは2の累乗です。

2の累乗10進数での重み
右から1桁目201
右から2桁目212
右から3桁目224
右から4桁目238
右から5桁目2416
右から6桁目2532
右から7桁目2664
右から8桁目27128

2進数101を10進数へ変換する

2進数101を、桁ごとの重みに分けてみましょう。

101

右から順番に、1、2、4の重みを割り当てます。

1 × 4
0 × 2
1 × 1

それぞれを足します。

4 + 0 + 1
= 5

したがって、2進数101は10進数の5です。

101(2進数)=5(10進数)

2進数1111011を10進数へ変換する

次に、2進数1111011を確認します。

2進数の桁重み計算
1641 × 64
1321 × 32
1161 × 16
181 × 8
040 × 4
121 × 2
111 × 1

1になっている桁の重みを合計します。

64 + 32 + 16 + 8 + 0 + 2 + 1
= 123

したがって、次の対応になります。

1111011(2進数)=123(10進数)

C17では2進整数定数を標準の書き方として使わない

この教材で基準としているC17では、2進整数定数を表す標準の接頭辞はありません。

一部のコンパイラでは、次のような書き方を独自機能として受け付ける場合があります。

0b1111011

しかし、これはC17の標準的な整数定数ではありません。

C17の範囲で移植しやすいコードを書く場合は、同じ値を10進数や16進数で記述します。

int decimal_value = 123;
int hex_value = 0x7B;

2進数の考え方は、ビットの並びを理解するために重要です。ソースコードでは16進数を使い、その値を頭の中で2進数に対応させる場面が多くあります。

8進数は0から7までを使う

8進数は、0から7までの8種類の数字を使います。

0 1 2 3 4 5 6 7

8進数では7の次に桁が上がり、10になります。

8進数の10は、10進数では8です。

8進数10進数
00
11
77
108
119
1715
2016

8進数の桁の重み

8進数の桁は、右から順番に次の重みを持ちます。

1、8、64、512、4096
8の累乗10進数での重み
右から1桁目8⁰1
右から2桁目8
右から3桁目64
右から4桁目512

8進数173を10進数へ変換すると、次のようになります。

1 × 82 + 7 × 81 + 3 × 80

計算すると、次の値になります。

64 + 56 + 3
= 123

したがって、8進数173は10進数の123です。

C言語では先頭の0が8進数を表す

C言語で8進整数定数を書くときは、先頭に0を付けます。

0173

C言語のソースコードに書かれた0173は、8進数の173という意味です。

その値は、10進数では123です。

int octal_value = 0173;

先頭の0も含めて、8進整数定数を表すための記述です。

8進整数定数に8や9は使えない

8進数で使用できる数字は0から7までです。

そのため、次の表記は正しくありません。

08
09

先頭が0なので8進数として解釈されますが、8進数では8と9を使用できません。コンパイルエラーになります。

10進数の先頭へ不要な0を付けない

次の2つは、見た目が似ていますが値が異なります。

int first_value = 123;
int second_value = 0123;

first_valueへ入るのは10進数の123です。

second_valueへ入るのは8進数の123であり、10進数では83です。

ソースコード上の表記進数10進数での値
12310進数123
01238進数83

桁をそろえる目的で、10進数の先頭へ0を付けるのは避けましょう。

16進数は0からFまでを使う

16進数では、0から9までの数字に加えて、AからFまでの英字を使います。

16進数10進数
00
11
99
A10
B11
C12
D13
E14
F15

16進数では、Fの次に桁が上がって10になります。

16進数の10は、10進数では16です。

英字のAからFは、文字としてではなく、10から15までの数字として使われています。

16進数の桁の重み

16進数の桁は、右から順番に次の重みを持ちます。

1、16、256、4096、65536
16の累乗10進数での重み
右から1桁目16⁰1
右から2桁目16¹16
右から3桁目16²256
右から4桁目16³4096

16進数7Bを10進数へ変換してみましょう。

Bは10進数の11です。

7 × 161 + B × 160

Bを11として計算します。

7 × 16 + 11 × 1
= 112 + 11
= 123

したがって、16進数7Bは10進数の123です。

C言語では0xまたは0Xを付ける

C言語で16進整数定数を書くときは、先頭に0xまたは0Xを付けます。

0x7B
0X7B

どちらも同じ値です。

AからFについても、大文字と小文字のどちらを使っても構いません。

0x7b
0x7B
0X7b
0X7B

これらは、すべて10進数の123を表します。

16進数がよく使われる理由

16進数は、2進数との対応が分かりやすいという特徴があります。

2進数4桁で表せる範囲は、次のとおりです。

0000から1111

10進数では0から15までです。

16進数の1桁も、0からFまでの16種類です。そのため、2進数4桁と16進数1桁を正確に対応させられます。

0000 → 0
0001 → 1
1010 → A
1111 → F

長い2進数を4桁ずつ区切り、16進数へ置き換えると短く読みやすくなります。

図2:10進数123を2進・8進・16進へ変換する

この図から分かること

10進数の123を2進数で表すと1111011です。

この2進数を右から3桁ずつ区切ると、8進数の173へ変換できます。

右から4桁ずつ区切ると、16進数の7Bへ変換できます。

8は23、16は24なので、次の対応が成り立ちます。

  • 2進数3桁は8進数1桁に対応する
  • 2進数4桁は16進数1桁に対応する

10進数123を2進数へ変換する

10進数を2進数へ変換する代表的な方法は、2で割り続けて余りを記録する方法です。

手順は次のとおりです。

  1. 10進数を2で割る
  2. 商と余りを記録する
  3. 商が0になるまで2で割り続ける
  4. 余りを最後から逆順に読む

123を2で割っていきます。

計算余り
123 ÷ 2611
61 ÷ 2301
30 ÷ 2150
15 ÷ 271
7 ÷ 231
3 ÷ 211
1 ÷ 201

余りを下から上へ読みます。

1111011

したがって、次のようになります。

123(10進数)=1111011(2進数)

2進数から8進数へ変換する

2進数から8進数へ変換するときは、右端から3桁ずつ区切ります。

123を表す2進数は、次のとおりです。

1111011

右から3桁ずつ区切ります。

1 111 011

左端の桁数が不足しているため、先頭へ0を補います。

001 111 011

値の先頭へ0を補っても、値そのものは変わりません。

それぞれの3桁を、8進数1桁へ変換します。

2進数3桁8進数
0011
1117
0113

左から並べると173になります。

1111011(2進数)=173(8進数)

C言語のソースコードで8進整数定数として書く場合は、先頭に0を付けます。

0173

2進数から16進数へ変換する

2進数から16進数へ変換するときは、右端から4桁ずつ区切ります。

1111011

4桁ずつ区切ります。

111 1011

左端に0を補います。

0111 1011

それぞれの4桁を、16進数1桁へ変換します。

2進数4桁10進数16進数
011177
101111B

左から並べると7Bになります。

1111011(2進数)=7B(16進数)

C言語のソースコードで16進整数定数として書く場合は、先頭に0xを付けます。

0x7B

4ビットと16進数の対応表

2進数4桁を4ビットと呼びます。

4ビットでは0000から1111までを表せます。この16種類が、16進数の0からFまでに対応します。

10進数2進数16進数
000000
100011
200102
300113
401004
501015
601106
701117
810008
910019
101010A
111011B
121100C
131101D
141110E
151111F

最初からすべてを暗記する必要はありません。

まずは、次のような特徴的な対応から覚えると分かりやすくなります。

0000 = 0
0001 = 1
1010 = A
1011 = B
1111 = F

8ビットは16進数2桁で表せる

8ビットの2進数は、4ビットずつ2つに分けられます。

11111111

4桁ずつ区切ります。

1111 1111

1111は16進数のFなので、次のようになります。

11111111(2進数)=FF(16進数)

C言語で16進整数定数として書く場合は、0xを付けます。

0xFF

同じように、次の対応も確認できます。

00001111(2進数)=0F(16進数)
11110000(2進数)=F0(16進数)

C言語では、それぞれ次のように書けます。

0x0F
0xF0

この対応は、後でビット演算やフラグ処理を学ぶときに役立ちます。

C言語における整数定数の書き方

C17では、整数定数を10進数、8進数、16進数で記述できます。

ソースコード上の表記意味10進数での値
12310進整数定数123
01738進整数定数123
0x7B16進整数定数123
0X7B16進整数定数123

2進数については、C17の標準的な整数定数として直接記述する方法がありません。

そのため、2進数の1111011を表したい場合は、同じ値を10進数の123や16進数の0x7Bで書きます。

値と表記方法を分けて考える

次の3つの変数は、異なる表記の整数定数で初期化されています。

unsigned int decimal_value = 123;
unsigned int octal_value = 0173;
unsigned int hex_value = 0x7B;

見た目は異なりますが、3つの変数へ保存される値は同じです。

変数ソースコード上の表記保存される値
decimal_value123123
octal_value0173123
hex_value0x7B123

変数の中へ、10進数、8進数、16進数という見た目が保存されるわけではありません。

進数の違いは、ソースコードへ値を書く方法や、画面へ値を表示する方法の違いです。

printfで表示する進数を選べる

printfでは、変換指定を変えることで、同じ値を10進数、8進数、16進数として表示できます。

変換指定表示形式123を表示した例
%u符号なし10進数123
%o8進数173
%x小文字を使う16進数7b
%X大文字を使う16進数7B

たとえば、同じ変数valueを異なる形式で表示できます。

printf("%u\n", value);
printf("%o\n", value);
printf("%x\n", value);
printf("%X\n", value);

表示結果は次のようになります。

123
173
7b
7B

変数へ保存されている値は同じですが、printfによる見せ方が変わっています。

%oや%Xでは接頭辞が自動的に付かない

%oで8進数を表示しても、通常は先頭の0が自動的に付きません。

printf("%o\n", value);

表示結果は173です。

173

%Xで16進数を表示しても、通常は0xや0Xが自動的に付きません。

printf("%X\n", value);

表示結果は7Bです。

7B

ソースコード上の0173や0x7Bと、printfによる表示結果の173や7Bを区別しておきましょう。

同じ値を異なる進数で確認するプログラム

次のプログラムでは、10進数、8進数、16進数で書かれた同じ値を比較します。また、1つの値を複数の表示形式で出力します。

同じ値を10進・8進・16進で表示するプログラム

ファイル名:4_6_1.c

// 同じ値を10進・8進・16進で表示するプログラム
#include <stdio.h>

int main(void)
{
    /* 同じ値を異なる進数の整数定数で表す */
    unsigned int decimal_value = 123;
    unsigned int octal_value = 0173;
    unsigned int hex_value = 0x7B;

    /* すべて10進数で表示して値を比較する */
    printf("10進整数定数123の値:%u\n", decimal_value);
    printf("8進整数定数0173の値:%u\n", octal_value);
    printf("16進整数定数0x7Bの値:%u\n", hex_value);

    /* decimal_valueを異なる形式で表示する */
    printf("123を10進数で表示:%u\n", decimal_value);
    printf("123を8進数で表示:%o\n", decimal_value);
    printf("123を16進数の小文字で表示:%x\n", decimal_value);
    printf("123を16進数の大文字で表示:%X\n", decimal_value);

    return 0;
}
実行結果の例
10進整数定数123の値:123
8進整数定数0173の値:123
16進整数定数0x7Bの値:123
123を10進数で表示:123
123を8進数で表示:173
123を16進数の小文字で表示:7b
123を16進数の大文字で表示:7B

3つの整数定数は同じ値を表す

次の3行では、異なる表記方法を使っています。

unsigned int decimal_value = 123;
unsigned int octal_value = 0173;
unsigned int hex_value = 0x7B;

decimal_valueは10進整数定数123で初期化しています。

octal_valueは8進整数定数0173で初期化しています。

hex_valueは16進整数定数0x7Bで初期化しています。

3つを%uで10進表示すると、すべて123になります。

printf("10進整数定数123の値:%u\n", decimal_value);
printf("8進整数定数0173の値:%u\n", octal_value);
printf("16進整数定数0x7Bの値:%u\n", hex_value);

この結果から、表記方法が違っても、保存される値は同じであることを確認できます。

%uで符号なし10進数として表示する

このプログラムでは、変数をunsigned int型として宣言しています。

unsigned int decimal_value = 123;

unsigned int型の値を10進数で表示するため、%uを使用しています。

printf("%u\n", decimal_value);

実行結果は123です。

%oで8進数として表示する

decimal_valueを%oで表示すると、8進数の173になります。

printf("%o\n", decimal_value);

実行結果は次のとおりです。

173

ソースコードへ8進整数定数を書くときは0173ですが、%oによる通常の表示では173になります。

%xと%Xで16進数として表示する

%xを使用すると、16進数のaからfが小文字で表示されます。

printf("%x\n", decimal_value);

実行結果は次のとおりです。

7b

%Xを使用すると、AからFが大文字で表示されます。

printf("%X\n", decimal_value);

実行結果は次のとおりです。

7B

値そのものは同じなので、読みやすさや表示目的に合わせて%xと%Xを選べます。

図3:C言語での整数表記とprintfの表示形式

この図から分かること

ソースコードでは、接頭辞によって整数定数の進数を指定します。

  • 接頭辞なしは10進整数定数
  • 先頭の0は8進整数定数
  • 先頭の0xまたは0Xは16進整数定数

printfでは、変換指定によって表示形式を選びます。

  • %uは符号なし10進数
  • %oは8進数
  • %xは小文字の16進数
  • %Xは大文字の16進数

値の書き方と表示方法は別に考えることが大切です。

先頭の0を見落とさない

C言語で特に間違えやすいのが、8進整数定数の先頭に付く0です。

int number = 0123;

この0123は、10進数の123ではありません。8進数の123なので、10進数では83です。

10進数の123を保存したい場合は、先頭に0を付けません。

int number = 123;

コードを読むときは、整数の先頭に0が付いていないか確認しましょう。

16進数のAからFを文字と混同しない

16進数に使われるAからFは、英字のように見えますが、16進数では数字の一部です。

A=10
B=11
C=12
D=13
E=14
F=15

たとえば、0x1FのFは文字定数ではありません。16進数の15を表す数字です。

1 × 16 + 15
= 31

したがって、0x1Fは10進数の31です。

2進数を4桁ずつ見る習慣を付ける

2進数が長くなると、1桁ずつ読むのは大変です。

次のように4桁ずつ区切ると、16進数へ置き換えやすくなります。

1101 1010

対応表を見ると、次のようになります。

1101 → D
1010 → A

したがって、次の対応になります。

11011010(2進数)=DA(16進数)

C言語で16進整数定数として書く場合は、次のようになります。

0xDA

小さい値で変換を練習しよう

進数の変換は、小さな値から練習すると理解しやすくなります。

10進数2進数8進数16進数
510155
81000108
10101012A
15111117F
16100002010
3111111371F
12311110111737B
25511111111377FF

特に次の対応は、ビット演算を学ぶときにもよく登場します。

15(10進数)=1111(2進数)=17(8進数)=F(16進数)
31(10進数)=11111(2進数)=37(8進数)=1F(16進数)
255(10進数)=11111111(2進数)=377(8進数)=FF(16進数)

進数のすべてを一度に暗記する必要はありません。

まずは、2進数3桁と8進数1桁、2進数4桁と16進数1桁の対応を意識してみましょう。同じ値を複数の表記で書き、printfの%u、%o、%x、%Xで表示して確かめると、数字同士のつながりが少しずつ見えるようになります。