C言語編 第19章 n進数

この章の概要

この章の概要です。

n進数

10進数は、普段の生活で使われる普通の数です。 すなわち、0〜9 までの 10種類の数字を使って、全ての数を表現します。

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

10進数を例に取りましたが、「10」の部分は他の数字でも構わないのです。 例えば、分や秒は 60進数で表現されていると考えられます。 このように一般化して考えるとき、まとめて n進数と呼びます。 また、n進数において、n の部分を基数または(てい)と呼びます。

ここまでのところをまとめると、基数n が桁上がりの基準であると言えます。 10進数の基数は 10 であり、10 のところで桁が上がる訳です。 そして、10 が 60 になっても全く同じことです。

2進数

プログラミングを学ぶ我々としては、2進数こそ最重要概念であると言えるかも知れません。 なぜなら、コンピュータは情報を 2進数で管理しているからです。 日頃、コンピュータを利用していてもそうは見えませんが、内部では 2進数、人間に見える部分には 10進数が使われているのです。

さて、n進数の考え方を理解していれば、2進数というものがどんな数か分かるはずです。 基数が 2 であり、従って、2 のところで桁上がりします。 ということは、扱える数字は 0〜1 の範囲だけですね。

このように、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進数の「1」は 1桁です。「10」なら 2桁ですし、「100」なら 3桁です。 では、これらはそれぞれ「1」を何倍したものでしょうか?
これらは、1倍、10倍、100倍…と増えています。 つまり、桁が増えるごとに、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 になるまで@を繰り返す。
  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 になります。 これは、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 です。


すでに、10進数と 2進数でうまくいくことが分かっている訳ですが、この方法はもちろん、何進数でも共通です。 前の項と同様、5進数で実験しましょう。 5進数の「333」を 10進数に変換します。

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

16進数

C言語のプログラム中に「100」と書けば、それは 10進数の 100 です。 10進数以外にも、幾つかの n進数は、たまに使う機会があるため、C言語には専用の記法が用意されています。

C言語で 16進数を表現するには「0x100」のように、数値の頭に「0x」を付けます。 すると、16進数の 100 であるとみなされます。

16進数は、「0〜9」と「A〜F」の合計 16種類の数字・文字を使って表現されます。 10〜15 までの数を 1桁として扱うために、アルファベットを導入する訳で、A が 10、B が 11 … そして、F が 15 を表します。
なお、「0x77E」を「0x77e」と書いても構いません(アルファベットの大文字・小文字は問いません)。


また、printf関数(⇒リファレンス)には、数値を 16進数で出力する機能があります。 10進数の場合に "%d" を使っていましたが、これを "%x" または "%X" に変えるだけです。 "%x" の場合は「A〜F」を小文字で出力し、"%X" の場合は大文字で出力します。 ただし、"%x" や "%X" は、負数を扱えません

同様に、scanf関数(⇒リファレンス)や sscanf関数(⇒リファレンス)でも、"%d" の代わりに "%x" や "%X" を使えます。 こちらは、"%x" と "%X" には違いが一切ありません(大文字・小文字が識別されると、かえって使いにくいですね?)

8進数

16進数のほかに、8進数のための記法も用意されています。 C言語で 8進数を表現するには「0100」のように、数値の先頭に「0」を付けます。

8進数は、「0〜7」までの合計 8種類の数字で表現します。


printf関数(⇒リファレンス)、 scanf関数(⇒リファレンス)、 sscanf関数(⇒リファレンス)は、8進数での入出力にも対応しています。 8進数を扱うには、"%o" を指定します。 ただし、"%o" は負数を扱えません

ちなみに、これらの関数に 2進数を扱う機能は、残念ながら存在しません。

対応表

よく登場する 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進数に長く触れていれば、ある程度自然に暗記できると思います。

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

ここまでに、10進数と n進数の相互変換(つまり、2通り)の方法だけしか紹介していません。 しかし、この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」となります。


練習問題

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

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

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


解答ページはこちら

参考リンク

新版 明解C言語 入門編
 -- 基数変換の方法。
プログラマの数学
 -- 一般的な数の捉え方について。

更新履歴

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

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

'2009/6/16 新規作成。



前の章へ

次の章へ

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

Programming Place Plus のトップページへ