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

先頭へ戻る

この章の概要

この章の概要です。

10進数

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

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

10進数を例に取りましたが、「10」の部分は他の数字でも構わないのです。 例えば、「分」や「秒」は、60 になったときに次の桁(次の単位)に進みますから、60進法で表現されていると考えられます。

このように一般化して考えたとき、表記方法をまとめて、n進法と呼び、 n進法で表記された数を、n進数と呼びます。 n の部分は、r などの他の文字で表すこともありますが、ともかく、何らか具体的な数が入るということを意味しています。 この n の部分のことを、基数と呼びます。

2進数

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

さて、n進数の考え方を理解していれば、2進数というものがどんな数か分かるはずです。 基数が 2 ですから、2 のところで桁上がりします。 したがって、扱える数字は 0~1 の範囲だけであり、1 の次は 10、11、100 … と続きます。

このように、2進数の 1桁で扱える数は 0 か 1 のわずか 2通りしかありません。 この 2進数 1桁分のデータを、ビットという単位で呼びます。

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

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

1ビットで表現できる範囲は 0~1 の 2通りですから、1バイトなら、その 8倍…ではなく、8乗になります。 28 ですから 256通りです。 なぜ 8乗になるのか考えてみましょう。

基数が n で一般化できるので、一旦、n == 10 として、つまり 10進数で考えてみましょう。
10進数の「1」は 1桁です。「10」なら 2桁ですし、「100」なら 3桁です。 これらは、1倍、10倍、100倍…と増えています。 つまり、桁が1つ増えるごとに、n倍されているのです。
例えば、10進数の「1」から 10進数の「1000」へは、桁数が 3桁増えていますが、数としては 1000倍に増えています。 これは、103 = 1000 だからです。

2進数の世界に戻って考えると、「1」が「1000」になったら、桁数は 3桁増えています。 ですから、数としては 23、つまり 8倍になったことになります。 これをもとに 1バイトで表現できる範囲を考えてみると、1バイト= 8ビットなので、 28 = 256 となり、256通りになることが分かります。

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

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

基数が 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進数なんて言われてもピンとこないですが…。

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

次に、2進数から 10進数へ変換することを考えます。 ここで重要なのは、各桁に重み付けをするという考え方です。

10進数の「345」という数があるとき、「3」の部分は実は「3」ではなく「300」です。 同様に「4」の部分は「40」だし、「5」の部分はそのまま「5」です。
これは、桁という考え方があるからですが、この考え方を式で表現すれば「3*100 + 4*10 + 5*1 = 345」です。 もっと詳しく書くと、「3*102 + 4*101 + 5*100 = 345」になります。 右肩に乗っている数字を指数と呼びます。

この例で、102、101、100 の「10」の正体は、基数です。 基数10 を、下位の桁から順に 0乗、1乗、2乗…としていった訳です。 そして、これが重みです。

なお、100 は 1 です。基数が幾つであろうとも、0乗すれば結果は 1 になります。

100 が 1 になるのは、次のように考えれば分かります。 101 を 10倍すると 102 に、更に 10倍すると 103 になりますが、 逆方向に考えていくと、指数が 1減るごとに、10分の1 になっていることが分かります。 101 を 10分の1 すると、結果は 1 ですから、100 は 1 になるのです。

さて、今度は 2進数で考えます。 例えば 2進数の「1011101」という数は、 「1*26 + 0*25 + 1*24 + 1*23 + 1*22 + 0*21 + 1*20」です。 つまり、「1*64 + 0*32 + 1*16 + 1*8 + 1*4 + 0*2 + 1*1」ですから、その結果は 93 です。
このようにして、2進数を 10進数に変換することができます。


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

5進数の「333」は、「3*52 + 3*51 + 3*50」ですから、「3*25 + 3*5 + 3*1」となり、 その結果は 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


対応表

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

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

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

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

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

のように、2つの過程を経れば良いのです。 試しに、2進数の 1101100 を 16進数に変換してみましょう。

まず、10進数に変換します。 この変換には、重み付けが必要でした。 元の数は 2進数なので、20、21、22 … といった具合に、下の桁から重みを付けていきます。
そして、各桁の値と、重みとを掛け合わせていき、その合計を計算します。

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

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

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

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

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


練習問題

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

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

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


解答ページはこちら

参考リンク

更新履歴

'2018/2/19 全面的に文章を見直し、修正を行った。
タイトルを変更(n進数→数の表現方法)

'2011/6/13 誤記の修正。

'2009/6/23 指数の表記を上付き文字に修正。補足説明を追加。

'2009/6/16 新規作成。





前の章へ(第18章 理解の定着・小休止②)

次の章へ(第20章 数値の大きさと符号)

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

Programming Place Plus のトップページへ


このエントリーをはてなブックマークに追加
rss1.0 取得ボタン RSS