C言語編 第18章 数の表現方法

先頭へ戻る

この章の概要

この章の概要です。


10進数

我々は普段の生活で、0~9 までの 10種類の数を使って、数を表記しています。これは、10進法と呼ばれる表記方法です。10進法で表記された数を、10進数と呼びます。

10進法では、0 から順番に数を増やしていったとき、10 のところで桁が増えます。これが「10進」という言葉の意味合いで、10 になると進む(桁が変わる)ということになります。

10進法では、数を 10 のべき乗と、10種類の数字(0~9) を使って表現します。例えば、345 という数は、「102 * 3 + 101 * 4 + 100 * 5」と考えればよいです(ある正の数の 0乗はつねに 1 です)。

小数点以下の値も同様に考えられます。例えば、345.67 であれば、「102 * 3 + 101 * 4 + 100 * 5 + 10-1 * 6 + 10-2 * 7」のことです。10-1 とは、10分の1 (0.1) のこと、10-2 とは 100分の1 (0.01) のことです。

基数

先ほどの 10進数の話の中で、「10」のべき乗とか、「10」種類の数字といったように、10 という値が現れました。この数を、基数といいます。

基数は 10以外であっても構いません。プログラミングの世界では、基数が 2、8、10、16 の数がよく用いられます。この先の項で、順番に見ていきます。

なお、基数を n と表現するとき、表現方法を n進法といいます。また、n進法で表記された数を n進数といいます。

2進数

プログラミングでは、2進法が非常に重要です。なぜなら、コンピュータは情報を 2進数で表現するからです。日頃、コンピュータを利用していてもそうは見えませんが、内部では 2進数が、人間に見える部分には(基本的に)10進数が使われているのです。

2進数の数値は、0、1、10、11、100 … のように増えていきます。つまり、2 のところで桁が変わります。

10進数のところで見たように、基数が 2 であることの意味は、数を 2 のべき乗と、2種類の数字(0~1) を使って表現するということです。例えば、2進数の 1001 という数は、「23 * 1 + 22 * 0 + 21 * 0 + 20 * 1」であることを意味しています。つまり「8 + 0 + 0 + 1」なので「9」のことです。2進法で表記した「1001」と 10進法で表記した「9」が同じ値であることを示しています。

なお、2進数の 1桁分のデータを、ビットという単位で呼びます。

ビットという単位とともに、バイトという単位もよく目にするはずです。ほとんどの場合、1バイトは 8ビットと同じことです。

ごくわずかではありますが、1バイトが 8ビットでない環境もあります。今後は、わざわざ断りを入れず、1バイト=8ビットという前提で進めます。

最後に、2進数の「1000」は「せん」とは呼ばないようにしましょう。「千」はあくまで 10進数の世界の「1000」です。2進数の「1000」は、10進数では「8」なので、「いちぜろぜろぜろ」と呼ぶのが確実です。

10進数を n進数へ変換する

基数が n の数を、異なる基数 m の数に変換する方法は知っておくべきです。まずは、10進数を 2進数に変換する方法を見てみましょう。

10進数を 2進数に変換するには、

  1. 元の数を 2 で割り、その余りを書き出す
  2. 元の数が 0 になるまで、(1) を繰り返す
  3. 書き出しておいた余りを、逆順に読み取ると、それが 2進数に変換した結果になっている

例えば、10進数の 93 なら、

)93)46・・・1
2)23・・・0
2)11・・・1
2) 5・・・1
2) 2・・・1
2) 1・・・0
   0・・・1

のように計算を行い、書き出された余りの部分を逆順に読み取ります。結果は「1011101」です。


この計算方法ですが、元になる数が 10進数でさえあれば、変換先が何進数であっても同じです。例えば、5進数に変換するのなら、5 で割ることを繰り返します。

)93)18・・・3
5) 3・・・3
   0・・・3

結果は「333」です。5進数なんて言われてもピンとこないですが…。

n進数を 10進数へ変換する

次に、2進数から 10進数へ変換する方法ですが、これは「2進数」のところで見た通りです。2進数の各桁は「2x * (0 または 1)」のことなので、ここから計算できます。

例えば 2進数の「1011101」という数は、「26 * 1 + 25 * 0 + 24 * 1 + 23 * 1 + 22 * 1 + 21 * 0 + 20 * 1」です。したがって「64 + 16 + 8 + 4 + 1」ですから、その結果は 93 です。

このようにして、2進数を 10進数に変換できます。


この方法は、10進数へ変換するのであれば、変換元が何進数であっても共通して使えます。前の項と同様、5進数で実験しましょう。5進数の「333」を 10進数に変換します。

5進数の「333」は、「52 * 3 + 51 * 3 + 50 * 3」ですから、「75 + 15 + 3」となり、その結果は 93 になります。

8進数

プログラミングでよく使われる n進数には、10進数、2進数の他に、8進数と 16進数があります。ここでは 8進数を取り上げます。

8進数は、8進法で表記された数です。8進法では、「0~7」までの合計 8種類の数字で数を表記します。7 の次で桁が増えるので、「…6、7、10、11…」と増えていきます。

C言語のソースコード上では、数値の先頭に「0」を付加すると、その数値を 8進数とみなすことになっています。例えば、「0100」は、8進数の 100 のことです。浮動小数点数を 8進法で表記することはできません。

稀に、この表記方法があることを知らなかったり忘れていたりして、ソースコード上の単なる桁数合わせのような感覚で、先頭に 0 を付けてしまうバグを見かけることがあります。「100」のつもりで「0100」と書けば、当然、違う数になってしまうので注意が必要です。


printf関数scanf関数sscanf関数は、8進数での入出力にも対応しています。8進数を扱うには、"%o" という変換指定子を使います。ただし、"%o" は負数を扱うことができません

例えば、printf関数で "%o" を試してみます。

#include <stdio.h>

int main(void)
{
    printf("%o\n", 0100);
    printf("%o\n",  100);
    printf("%d\n", 0100);
    printf("%d\n",  100);

    return 0;
}

実行結果:

100
144
64
100

ソースコード上の数の表記が何進法であるかと、printf関数の変換指定子を何にするかは対応する訳ではありません。ソースコード上では 8進法を使っていても、10進法で表記した出力結果が欲しいのなら "%d" 変換指定子を使えば良いです。

また、ソースコード上で「64」と書いても、「0100」と書いても、それは同じ数であることをよく理解して下さい。異なるのは表記方法であって、数値が違っているのではありません。

"%o" 変換指定子による出力結果の方には、数の先頭に「0」が付加されていません。もし、「0」を付けて欲しいのであれば、変換フラグと呼ばれる文字を付け足して、"#o" という変換を指定します。

#include <stdio.h>

int main(void)
{
    printf("%#o\n", 0100);

    return 0;
}

実行結果:

0100

16進数

続いて、16進法です。16進法では、「0~9」と「A~F」の合計 16種類の数字と文字を使って表記します。アルファベットの部分は、大文字でも小文字でも構いません。16進法で表記された数を、16進数と呼びます。

16進法では、「…7、8、9」と増えていった数は、次に「A、B、C、D、E、F」と増えます。10進数でいえば、A が 10、B が 11、F が 15 を意味しています。F の次で桁が増えて、16進数の「10」になります。

C言語のソースコード上では、数値の先頭に「0x」または「0X」を付加すると、その数値を 16進数とみなすことになっています。例えば、「0x100」は、16進数の 100 のことです。(C95規格までは)浮動小数点数を 16進法で表記することはできません。

C99 では、浮動小数点数のリテラルを 16進数で表記することができるようになりました。第20章で取り上げます。


printf関数は、16進数での入出力にも対応しています。16進数を扱うには、"%x" または "%X" という変換指定子を使います。"%x" の場合は「A~F」を小文字で出力し、"%X" の場合は大文字で出力します。なお、"%x" や "%X" は負数を扱うことができません

同様に、scanf関数sscanf関数でも、"%x" や "%X" を使います。こちらは、"%x" と "%X" には一切違いがなく、「A~F」でも「a~f」でも受け付けてくれます。入力の場合は、区別を付けない方が使いやすいはずです。

printf関数で "%x" と "%X" を試してみます。

#include <stdio.h>

int main(void)
{
    printf("%x\n", 0x1f);
    printf("%x\n", 0x1F);
    printf("%X\n", 0x1f);
    printf("%X\n", 0x1F);

    return 0;
}

実行結果:

1f
1f
1F
1F

n進数から m進数へ変換する

ここまでに、10進数から n進数の変換と、n進数から 10進数の変換だけを紹介しています。しかし、この2つさえ知っていれば、n進数と m進数の変換は常に行えるはずです。例えば、4進数を 7進数に変換したいとすれば、

のように、2つの過程を経れば良いのです。

試しに、2進数の 1101100 を 16進数に変換してみましょう。

まずは、10進数に変換します。これは、次の計算式で行えます。

「26 * 1 + 25 * 1 + 24 * 0 + 23 * 1 + 22 * 1 + 21 * 0 + 20 * 0」となり、「64 + 32 + 8 + 4」となるので、結果は 108 になります。

この段階で、10進数の 108 が得られました。ここから、更に 16進数への変換作業を行います。10進数からの変換の場合は、変換後の基数で割る作業を繰り返し、余りを逆方向に読み取っていくのでした。

16)108
16)  6・・・12
     0・・・6

余りを逆方向から読むと「6 12」ですが、16進数なので「12」は「C」です。従って結果は、「6C」となります。

こうして、2進数の 1101100 を、16進数の 6C に変換できます。


対応表

よく登場する 2・8・10・16進数の対応表を載せておきます。

2進数8進数10進数16進数
0000
1111
10222
11333
100444
101555
110666
111777
10001088
10011199
10101210A
10111311B
11001412C
11011513D
11101614E
11111715F

相互の変換方法を理解していれば、これを丸暗記する必要はありませんが、長い間、これらの数に親しんでいると、ある程度は自然に暗記できると思います。

2進数の負の整数

今度は負の整数を表現する方法について確認しましょう。コンピュータでは 2進数を用いるということなので、ここでは 2進数の例を挙げます。

2進数で負の整数を表現するには、3つのよく知られた方法があります。

ここでは、圧倒的によく使われている 2 の補数表現という手法を取り上げます。

C言語では、実行環境がどの表現を使っていても良いことになっています。

2 の補数とは、n桁の 2進数で表される整数 x があるとき、「x + y = 2n」を満たす y のことです

2 の補数表現では、正の整数 x の符号を変えた -x を表現するために、x の 2 の補数を使う手法です。なお、2 の補数表現では、最上位のビットが 0 のときは正の数であり、1 のときは負の数であるものとします

例えば、「0111」という 4桁の 2進数があるとします。最上位が 0 なので、これは正の数であり、+7 のことです。「0111」の 2 の補数は、先ほどの式の通り「0111 + y = 10000」となる y のことですが、この y の値を -7 を表現するものして扱います。

計算してみると「10000 - 0111」なので、y は「1001」であることが分かります。よって「0111」が +7、「1001」が -7 です。

このように普通に計算しても良いですが、2 の補数は、以下の手順で簡単に求められます

  1. 各ビットを反転する (0 は 1 に、1 は 0 に)
  2. +1 する (桁が溢れた部分は無視する)

「0111」を例に取ります。各ビットを反転すると「1000」です。ここに +1 すると「1001」が得られます。先ほど、普通に計算した結果と一致しました。

ちなみに、手順1で止めると 1 の補数になっています。1 の補数表現を使う世界では、「0111」が +7 を、「1000」が -7 を表します。

符号と絶対値による表現を使う世界では、最上位のビットを符号の表現に使い、残りのビットで絶対値を表現します。符号のビットが 0 ならば正、1 ならば負とします。この表現では、「0111」が +7、「1111」が -7 を表していることになります。

逆の操作をすれば、負の整数を正の整数に変換できます。つまり、次の手順を踏みます。

  1. -1 する
  2. 各ビットを反転する (0 は 1 に、1 は 0 に)

この方法を知っておけば、最上位ビットが 1 になっている負の 2進整数が与えられたとき、それが 10進数でいくつのことなのか分かります。例えば「1100」で試してみます。最上位ビットを見れば負数なのは明らかですが、具体的な数は単純には分かりません。

  1. -1 して「1001」
  2. 各ビットを反転して「0110」

「0110」は +6 のことなので、元の数「1100」は -6 のことだったと分かります。

2 の補数表現では、負の数の方が、正の数よりも表現できる値が1つ多いという特徴があります。

例えば、4桁の 2進数において表現できる最大の正の数は「0111」です。正の数であるためには、最上位ビットは 0 でなければならず、残りのビットがすべて 1 のときが最大値です。

ここで「1000」は -8 のことなのですが、+8 を表現することはできません。負の整数を正の整数に変換する手順を実行してみると、以下のようになります。

  1. -1 して「0111」
  2. 各ビットを反転して「1000」

このように「1000」は「1000」に戻ってしまいます。最上位ビットが 1 であるため、これは負の数を表しており、-8 に対応する正の数(+8) は表現できないということが分かります。

したがって、2 の補数表現を使う 4桁の 2進数の世界で表現できる値の範囲は、-8~+7 です。これは桁数が変わっても同じで、つねに負数の側の表現力が 1つ分多くなります

そのため、符号を反転させる処理においては、ごく限られたケース(負の最小値を反転させるとき)にだけ、結果が不適切なものになることに注意しなければなりません。よく、負の最小値の絶対値を求めるときに問題になります(第48章

1 の補数表現や、符号と絶対値による表現では、正と負とで表現範囲に違いはありません。その代わり、0 を表現する方法が2通りあるという別の特徴があります(原理上、+0 と -0 があり得る)。


練習問題

問題① 標準入力から 16進数の整数を受け取り、それを 8進数で出力するプログラムを作成して下さい。

問題② 8・10・16進数の対応表を表示するプログラムを作って下さい。値の範囲は、10進数で「0~16」とします。

問題③ 次の式を手作業で計算してみて下さい。そして、答えを確認するためのプログラムを作成して下さい。
「0x3c5 × 26 - 0741」は 10進数で幾つ?

問題④ 2進数の 0111101 を 10進数と 16進数に変換して下さい。


解答ページはこちら

参考リンク



更新履歴

'2018/6/6 全体的に構成と内容を修正。
2進数の負の整数」の項を追加。
参考書籍を追加。

'2018/6/5 第19章から移動してくる形で新規作成。



前の章へ(第17章 処理の流れを制御する)

次の章へ(第19章 整数型)

C言語編のトップページへ

Programming Place Plus のトップページへ


はてなブックマーク Pocket に保存 Twitter でツイート Twitter をフォロー
Facebook でシェア Google+ で共有 LINE で送る rss1.0 取得ボタン RSS
管理者情報 プライバシーポリシー