二進数
十進数と二進数
私たちは日常生活において十進数を利用しています. 十進数で表現される数では,それぞれの位が10に達したとき,繰り上がりが発生します. 逆に言うと,それぞれ桁の数が10になることはありません. このとき,「基数または底(英語では base または radix)が10である」と言います.
それでは,二進数を見てみましょう. 上述の通り,二進数は「基数(底)が2である数」となります. 十進数では,ある位の数が10になることはありませんでした. 同様に,二進数では,ある位の数が2になることはありません. 二進数の全ての桁では,2未満の数,すなわち0か1のいずれかしか現れないわけです. 別の言い方をすれば,1と0のみで数の表現をしています.
ただし,十進数の全ての数が表現できるわけではありません. 整数に限れば,十進数と二進数の関係を1対1で定義することは可能です. このとき,十進数上の整数と,二進数上の整数には,全単射な関係があるといえます. しかし,非整数については,この限りではありません. 有理数であっても,例えば十進数の$0.1 = \frac{1}{10}$を,二進数上で厳密に表現することはできません.
二進数を使用する際には注意が必要です. 例えば$1001$という数が現れたときに,私たちはそれが二進数であるかを判別することはきっと容易ではないでしょう. ほとんどの場合は,普段使い慣れている十進数として,「千と一」と認識すると思います. そこで,二進数を表記するときはこれは二進数であると明記してあげると,親切ですね. 以下では,二進数の表記法の例を3つ示します.
- $1001_2$
- $1001)_2$
- $(1001)_2$
上記の表記に差はありません. いずれも,これは二進数であるということを主張しています. この表記方法は,$n$進数においても有効です.
なおC言語では,0b1001
のように,先頭に0b
を付すことで2進数を表現することができます.
数学的定義
では, 二進数を数学的に定義しましょう. $k$を正の整数として,ある二進数における少数点から左側$k$番目の位の値を$a_k$(当然0または1)とすると,二進数の十進表現は以下のように定義できます. \begin{equation} \sum_k\, a_k \times 2^{k-1} . \end{equation}
いくつか例を見てみましょう. 例えば,$(110)_2$や$(1001)_2$は次のように十進数へと計算することができます.
$(110)_2 = 1\times 2^{3-1} + 1\times 2^{2-1} + 0\times 2^{1-1} = 1\times 4 + 1\times 2 + 0\times 1 = 4 + 2 = 6.$
$(1001)_2 = 1\times 2^{4-1} + 0\times 2^{3-1} + 0\times 2^{2-1} + 1\times 2^{1-1} = 1\times 8 + 0\times 4 + 0\times 2 + 1\times 1 = 8 + 1.$
これは小数にも拡張できます. $l$をの正の整数として,小数点から右側$l$番目の位,すなわち少数第$l$位の値を$a_l$とすると,次のように小数を計算できます. \begin{equation} \sum_l\, a_l \times 2^{-l} = \sum_l\, a_l \times \frac{1}{2^l}. \end{equation} こちらの具体例も見てみましょう.
$(0.1)_2 = 1\times 2^{-1} = 0.5 .$
$(1.011) = 1\times 2^{1-1} + 0\times 2^{-1} + 1\times 2^{-1} + 1\times 2^{-3} = 1 + 0 + 0.25 + 0.125 = 1.375 .$
後者のように,整数部と小数部の計算は同時に実行可能です.
ここで,先ほどの,「0.1が厳密に表現できない」という話を思い出してください. 二進数においても,小数を表現すること自体は可能です. しかしながら2の負冪の和では,どう頑張っても,0.1という数を丁度正確に表現することはできません. 数学的に言えば,全ての数に対して二進数から十進数への変換写像は全射ではないのです. とはいえ,ぴったり同じではなくとも,ほとんど同じ数,つまり近似された数を表現することはできます. 整数でない数を十進数から二進数に変換する際には近似が発生する,ということを頭に入れておくと良いでしょう.
なぜ二進数なのか
全ての数を表現できないならダメじゃないか!と思う方もいるかもしれません. しかし, 計算機を使う以上, 二進数を使う必要があるのです. 現代のほとんどの計算機上では,データはビット (bit) と呼ばれる単位で取り扱われます. 1 bitの情報は0か1のいずれかであるという情報しか持つことができません. これは計算機におけるデータの記憶方法が,高電位と低電位の2種類によるものだからです. このビットという単位の情報を扱うには,同じく0と1の2つの値のみを使う二進数が適しているというわけです.
もしビットという単位を聞いたことがなくても,バイト (B, byte) という単位を聞いたことのある方は多いのではないでしょうか. これらは密接な関係にあり, 2008年にIEC 80000-13という規格で,8 bit = 1 Bであると標準化されています.
十六進数
計算機上ではビット単位で情報を取り扱いますが,実際にデータとして取り扱うときは,8, 16, 32, 64 bitなどで扱うことが多いです. そのため,前節で紹介したバイトという8 bitの情報単位で取り扱うと便利です. ここで,2進数のままでは,0や1が16桁ずつ並ぶことになってしまい扱いにくいため,二進数4桁を1桁とする十六進数を導入すると便利です. 十進数,二進数と十六進数の対応表を,下表に示します.
十進数 | 二進数 | 十六進数 |
---|---|---|
0 | 0 | 0 |
1 | 1 | 1 |
2 | 10 | 2 |
3 | 11 | 3 |
4 | 100 | 4 |
5 | 101 | 5 |
6 | 110 | 6 |
7 | 111 | 7 |
8 | 1000 | 8 |
9 | 1001 | 9 |
10 | 1010 | A |
11 | 1011 | B |
12 | 1100 | C |
13 | 1101 | D |
14 | 1110 | E |
15 | 1111 | F |
16 | 10000 | 10 |
十六進数でも二進数と同様に,
- $1A_{16}$
- $1A)_{16}$
- $(1A)_{16}$
のように表記すると,区別が付いて良いでしょう.
なおC言語では,0bx1001
のように,先頭に0x
を付すことで16進数を表現することができます.
浮動小数点数
規格
数には符号による正負という性質があり,また絶対値が大きい・小さい数があります. 一方で計算機のリソースには上限があり,無限の桁を格納することはできません. そこで,浮動小数点数 (floating point number) という,計算機の資源を有効に使って数を表現できる方法を規格化するに至りました. 本書で取り扱っているC11も例外ではなく,IEEE 754という現在の国際標準規格に準拠しています. この規格では,単なる数の表現法だけではなく,誤差の丸め方や演算方法といった操作についてまで,部分まで細かく制定されています.
数の表現の規格を標準化するということは非常に重要です. 例えば,単3電池がどのメーカ製のものでも同じ大きさ・同じ電圧なのは,標準規格が存在するからです. もしこれがメーカ別だったら,困りますよね? プログラミングにおいても,同じようなことが多々あります. 異なるプログラミング言語で記述したアプリケーションが数値をやり取りをする場合でも,それぞれのプログラミング言語が標準規格に従って浮動小数点数を扱っていれば,問題なくやり取りが行えます. もし表現がプログラミング言語によって異なってしまったら,文字化けのような恐ろしいことが起きてしまうわけですね.
以下では,浮動小数点数規格:IEEE 754について簡単に説明を行います. 詳細を知りたい方は,各自で調べてみてください.
小数の指数表記
少数の表記法として, 指数表記があります. 例えば普段使う例として, $32000 = 3.2 \times 10^4$と表すことができます. このとき,3.2を仮数,$10$の肩に乗っている$4$を指数と呼びます.
二進数の場合も同様の指数表記ができます. $(1001)_2=(1.001)_2 \times 2^{(11)_2}$と書くとき,$(1.001)_2$を仮数, $(11)_2$を指数と呼びます.
単精度浮動小数点数
単精度浮動小数点数 (single precision floating point number) は,32ビットのデータを持ち,C言語ではfloat型として利用されています. 単精度浮動小数点数では,十進数において有効数字7桁程度の精度を出すことができます. 32ビットの内訳を,下表と下図に示します.
意味 | 領域 | 図中の色 |
---|---|---|
符号 | 先頭1ビット | 赤 |
指数+127 | 後続8ビット | 青 |
仮数 | 終端23ビット | 緑 |
倍精度浮動小数点数
倍精度浮動小数点数 (double precision floating point number) は,64ビットのデータを持ち,C言語ではdouble型として利用されています. 倍精度浮動小数点数では,十進数において有効数字15桁程度の精度を出すことができます. 64ビットの内訳を,下表と下図に示します.
意味 | 領域 | 図中の色 |
---|---|---|
符号 | 先頭1ビット | 赤 |
指数+1023 | 後続11ビット | 青 |
仮数 | 終端52ビット | 緑 |
非数と無限
C11においては, 浮動小数点数において無限 (Inf: infinity) と非数 (NaN: not a number) の値が存在します. これら2つはバグの温床となるので,プログラム上での発生を確認した場合は,速やかに原因を取り除きましょう.
無現値を表わすInf
は符号付きの値で,0または0に限りなく近い小さな値で割る計算を行った場合に発生します.
Inf
を含む加算や乗算,いわゆる無限の定形計算の結果はInf
になります.
非数値NaN
は,Inf - Inf
やInf / Inf
のような,いわゆる無限の不定形計算において発生します.
NaN
が含まれる演算の結果は,全てNaN
になります.
そのため,一度NaN
が発生してしまうと,その後の計算結果が全てNaN
に汚染されてしまうという可能性もあります.
数の表現の限界
オーバーフロー
計算機では,ある変数のデータサイズ上限は事前に決まっており,保持できる桁数には限りがあります. そのため桁数の不足により,計算の結果が本来得られるべき結果と合致しないことがあります. この現象をオーバーフロー (overflow),あるいは算術オーバーフロー (arithmetic overflow) と呼びます.
簡単な例を考えてみましょう. 取り扱えるデータのサイズが2ビットに制限された計算機で,本来$(10)_2 + (11)_2 = (101)_2$となる計算を実行してみることにしましょう. すると,繰り上がり桁が消え,$(10)_2 + (11)_2 = (01)_2$となってしまいます. また,この計算機で,$(111)_2$を代入しようとしても,代入の結果は$(111)_2$にはなりません. これもオーバーフローの一種です.
別の例も考えてみます. ある変数の取れる範囲が-127 ~ 127であるとします. この変数が,127のときに1を加算すると,-127になってしまう,といったことがあります. 同様に,その変数が-127のときに1を減算すると,127になってしまうということもあります. これらのいずれも,オーバーフローに該当します.
アンダーフロー
浮動小数点数の計算において,とても大きい数ととても小さい数を足し合わせたときに,小さい方の数が仮数部に入りきらず,結果として切り捨てられてしまうことがあります. この現象をアンダーフロー (underflow) ,あるいは算術アンダーフロー (arithmetic underflow) と呼びます.
計算機における数値の扱いの注意
この章では,計算機において,数がどのように表現されるのかについて説明してきました. 計算機で数を取り扱うときに気をつけるべき点を,以下にまとめます.
- 二進数では全ての十進数を厳密に表現することはできない.
- 計算機で使えるデータの大きさは有限であり,有限桁までしか表現できない.
特に後者は,数学で頻繁に用いる円周率$\pi$やネイピア数$e$が近似的な値としてしか利用できないことを意味します. プログラムで計算を実行する前に,あなたが行いたい計算にはどの程度の精度(有効数字桁数など)が要求されているのかということを,事前に確認しておくと良いでしょう.
(c) 2017-2021 Yuki Onishi