整数と浮動小数点数


はじめに

計算機でよく使われてる数値として、 がある。これらの内部フォーマットについて簡単にまとめる。

整数型

C言語で言えば、 整数はその長さに応じて、

char8bit整数(文字型)
short int16bit整数
long int32bit整数
int32bit整数

のような数値がある。 これは現在最も普及しているシステムでの 数値であり、これとは異なる場合もある。

また、整数型には符号無し整数と符号付き整数の2種類がある。C言語で言えば

unsigned int a;
signed int b;
のようにunsignedまたはsignedを 付加でき、unsignedならば0または正の数を、 signedは正負の数を表現できる。 何も付けない場合はsigned扱いに なる。

符号無し整数

まず、unsigned int型が計算機内でどう表現されているか説明する。 以下に、bit patternと数値との対応を示す。

bit pattern 数値
11111111111111111111111111111111 4294967295
00000000000000000000000000000010 2
00000000000000000000000000000001 1
00000000000000000000000000000000 0

つまり普通の2進数であり、32個の各bitが次のような重みを持っている。

231 230 229 ..... 21 20

上記のように、nビットで 0~2n-1の範囲の数を表現できる。 すなわち、Cのunsignedな整数型は、

char0~255
short int0~65535
long int0~4294967295

の範囲の数が表せることになる。

符号付き整数

次に、signed int型は計算機内でどう表現されているか説明する。 1bitを削ってそこを符号bitにし、残りの31bitに絶対値を入れればいいような 気もするが、それとは異なるフォーマットになっている。 以下に、bit patternと数値との対応を示す。

bit pattern 数値
01111111111111111111111111111111 2147483647
00000000000000000000000000000010 2
00000000000000000000000000000001 1
00000000000000000000000000000000 0
11111111111111111111111111111111 -1
11111111111111111111111111111110 -2
10000000000000000000000000000000 -2147483648

このような負の数の表現形式を「2の補数形式」と呼ぶ。 32個の各bitが次のような重みを持っていると考えることもできる (先頭だけ重みが負になっている)。

-231 230 229 ..... 21 20

上記のように、2の補数形式の場合、nビットで -2n-1~2n-1-1の範囲の数を表現できる。 すなわち、Cのsignedな整数型は、

char-128~127
short int-32768~32767
long int-2147483648~2147483647

の範囲の数が表せることになる。このようなフォーマットを採用している理由は、 オーバーフローを無視したunsigned用の32bit加算算を、そのままsigned用の 加算器として使えることが大きな理由として挙げられる。

浮動小数点数

次に、浮動小数点数 (C言語でのfloatdouble) について説明する。 これらは、基本的に「浮動小数点形式」と呼ばれる形式で格納されている。 例えば、「1234.5」に対する「1.2345×103」のように、 小数点の位置を1番左の数値と左から2番目の数値の間に移動し (この作業を「正規化」と呼ぶ)、それに指数を掛けた形式である。 この「1.2345」の部分を「仮数部」といい、「103」の部分を「指数部」と いう。

浮動小数点数は仮数部の長さ、指数部の長さ、指数が2か10か16か、など、 様々なバリエーションが考えられ、実際昔は計算機メーカー毎に様々な フォーマットが乱立していた。 そこで、1985年にWilliam Kahanが中心となって

IEEE 754: Standard for Binary Floating-Point Arithmetic
という標準規格が制定された。幸いなことにこれ以降に世に出たハードウェアの ほぼ全てがこの規格に従うこととなった。 以下、IEEE 754 Std.のフォーマットについて説明する。 倍精度(C言語で言うところのdouble)、単精度(C言語で言うところのfloat)の 2種類が規格化されている。

倍精度浮動小数点数

まず、倍精度を説明する。 倍精度は、符号(±)に1bit、指数部に11bit、仮数部に 52bit使って表す。全部で64bit=8byteである。

1(符号) 11(指数部) 52(仮数部)

すなわち、符号部をs、指数部を11bit符号無し整数と解釈したものをe、 仮数分のbitの並びをmとしたとき、それが表す数値xは、
x = (-1)s × 1.m × 2e-1023
のような式で書ける。

例えば、5.25は2進数で書くと

  101.01 = 1.0101×22
であるから、計算機内では、

0 1000000001 0101000000000000000000000000000000000000000000000000

のように格納されている。指数部の「1000000001」は、「2+1023=1025」 を2進数にしたものである。

ここまでは比較的簡単であるが、IEEE 754 Std.には複雑な例外事項がある。 以下それについて説明する。 指数部eは11bit符号無しなので0から2047まで取れるが、 その両端の数 (0と2047) は特殊な数を表すのに使われていて、 上で説明した規則が使われるのは指数部が1から2046まで (1023を減じた実際の指数では-1022から1023まで) の間である。

まとめると、次の表のようになる。

- m = 0 m ≠ 0
e = 0 ±0 x = (-1)s × 0.m × 2-1022
1 ≤ e ≤ 2046 x = (-1)s × 1.m × 2e-1023
e = 2047 ±∞ NaN (Not a Number)

正の正規化数の最大の数は、 e=2046, m=111...111なので、

1.111...111 × 21023 = 21024-2971 ≈ 10308.25
であり、これを超えた数は無限大になる。これを一般にオーバーフローという。 また、正の正規化数の最小の数は、e=1, m=000...000なので、
1.000...000 × 2-1022 = 2-1022 ≈ 10-307.65
であり、これを下回る数を0にすることを一般にアンダーフローという。 しかし、IEEE 754 Std.では、ここでアンダーフローさせずにある「悪あがき」をしている。これが非正規化数である。 非正規化数は、文字通り「正規化していない」数である。 正規化数の最小数の最終bitを1だけ減じると、
0.111...111 × 2-1022
となり、これを正規化すると
1.111...110 × 2-1023
となって、指数部の限界を超えてしまう。そこで、 「2-1022を下回ったら正規化をやめて指数部を2-1022に固定して仮数部をそのままmとして格納する」 という約束にする。これが非正規化数である。これにより、
0.111...111 × 2-1022
0.111...110 × 2-1022
...
0.100...000 × 2-1022
0.011...111 × 2-1022
...
0.000...001 × 2-1022 = 2-1074 ≈ 10-323.31
のような数が表現できるようになる。ただし、これらの2-1022と2-1074の間の数は、本来53bitあるべき仮数部の長さが52bit∼1bitまで減ってしまっており、精度が低下していることに注意する必要がある。

単精度浮動小数点数

単精度浮動小数点数(C言語で言うところのfloat)は、 全部で32bit=4byteで、

1(符号) 8(指数部) 23(仮数部)

のようになっている。指数部のオフセットが1023から127になるなどの違いは あるが、ほとんど倍精度と同じ。表にまとめると次の通り。

- m = 0 m ≠ 0
e = 0 ±0 x = (-1)s × 0.m × 2-126
1 ≤ e ≤ 254 x = (-1)s × 1.m × 2e-127
e = 255 ±∞ NaN (Not a Number)

丸めについて

倍精度(または単精度)浮動小数点数同士の演算(加減乗除など)の結果は、 倍精度(または単精度)浮動小数点数で表せるとは限らない。 そのときは、「その値に近い倍精度(または単精度)浮動小数点数で代用する」という ことが行われる。これが丸めである。 一般的には、その数の上と下の表現可能な数のうち近い方で代用する。

例えば10進数で仮数部3桁の浮動小数点演算を考え、2/3を計算すると、

2.00×100 / 3.00×100
=0.66666666...×100 =6.6666666...×10-1
となり仮数部3桁には収まらないが、仮数部の4桁目で例えば四捨五入を行うと、
6.67×10-1
のようになる。このときの計算値と真値との差
6.67×10-1 - 6.6666666...×10-1
=3.3333333...×10-4
が丸め誤差である。

IEEE 754 Std.では、2進数なので、基本的には0捨1入で丸めが行われる。 例えば、10進数の「0.1」をIEEE 754 Std.のdoubleに変換してみる。

  0.1 (10)
  = 0.000110011001100110011... (2)
  = 1.10011001100110011...×2-4 (2)
である。e-1023=-4だからe=1019であり、
  1019 (10)
  = 01111111011 (2)
である。仮数部は無限小数になっているのでそのまま格納出来ない。小数点以下は、 52bit以内とそれ以降で区切って表示すると、

1001100110011001100110011001100110011001100110011001 10011001100....

となる。はみ出た部分の先頭が「1」なので、0捨1入で繰り上げる。最終的には、 計算機内では、

0 01111111011 1001100110011001100110011001100110011001100110011010

のように格納されている。すなわち、10進数の0.1は計算機には正確に格納できず、少しだけ0.1より大きい値で格納されていることが分かる。

(偶数丸めはまだ書いてない。)

Machine Epsilon

一般に、浮動小数点数のフォーマットの精度を表す目安として、 machine epsilonが用いられる。 IEEE 754 Std.においては、 である。 この値は、一回の演算で混入する誤差の、計算結果の絶対値に対する相対的な 大きさを表す。
整数と浮動小数点数