浮動小数点数とIEEE754
演算誤差を考えるために、コンピュータの小数計算の理解を深めたい。
IEEE754
計算機で小数を扱う方法はいろいろあるが、標準仕様としてIEEE754が広く受け入れられている。
IEEE754では単精度浮動小数点数を32bit、倍精度浮動小数点数を64bitで表現する。説明の便宜上、前者をfloat・後者をdoubleと呼ぶことにする。浮動小数点数の話は特定の言語に限定されるものではないのだが、その方が読みやすいと思うので許してほしい。また話を簡単にするために、ここでは浮動小数点数=floatを前提にして話を進める。
小数の指数表現
どのような小数(実数)でも、指数表現を使えば簡潔に書ける。
IEEE754の浮動小数点数は2進数の指数表現を使って任意の小数を表す。IEEE754の仕様では、floatは23bitの仮数部と8bitの指数部を、doubleは53bitの仮数部と11bitの指数部を持つ。
ここまではよく聞く話だが、実際にどのような値が格納されるかまでは意識する機会がない。
例えば仮数部は、「整数部が1であるような2進小数の小数部分」である。これはどういうことだろうか。
2進小数の指数表現
10進数を2進数に変換するとき、通常は...の位を考える。
同様に、...の位を考えれば二進小数になる。
10進数を2進数に変換するには以下の計算を行えばよい。
- 整数部を2で割り続けて、余りが出れば対応する位は1
- 小数部に2をかけ続けて、1を超えれば対応する位は1
やってみればどうということはない。
例えば を2進数に変換すると、となる。
これを指数表記に直せば、
となる。下線部が冒頭で言った「整数部が1であるような2進小数の小数部分」であり、ここから23bitが仮数部に保存される。
IEEE754と指数部
他方、2進小数を指数表記したときの指数が指数部に保存される。floatの指数部は8bitあるので-128~127を指定できる・・という訳ではないようだ。
IEEE754によると、指数部で表現できる値は-126~127である。-128, -127は特殊な状態を表すために犠牲になった。また、bit表現は指数の値に127(0b01111111)を加算した値とするようだ
- -128 (0b10000000) + 127(0b01111111) = -1(0b11111111) ... NaN, Inf
- -127 (0b10000001) + 127(0b01111111) = 0(0b00000000) ... 非正規化数, 0
非正規化数については後述。ようは指数部の限界を超えた小さい数である。
指数部がオール0またはオール1でなければ正常(正規化数)だと判断できる。
表現できる数字の限界
float/doubleは仮数部がせいぜい23/53bitしかないので、表現できない数字が出てくる。floatの有効桁数は2進小数の小数部23bitと暗黙の1bitの整数部を含めた24桁である。により、10進数では7桁相当となる。これは、10進数で有効桁数が8桁の数字をfloatでは正確に表現できないことを意味する。
いろいろなビット配列を考えながら、表現できる数字の限界を探ってみよう。
大きな数字
例えばは2進数で0が23個続く整数である。この値をfloatに代入すると仮数部が0で埋まる。
は2進数でN+1桁の数字となる。
次の小数は2進数で有効数字が25桁ある。24bitを超えた分の数字は丸められる。
以降の整数は整数ですら丸め処理が発生する。
以降、桁数が増えるにつれて誤差も大きくなる。以上、未満の整数は飛び飛びでしか表現できなくなり、その差はとなる。
これは浮動小数点数の性質として知られている。浮動小数点数が表現できる数字は離散的で、その幅は0から離れるほど大きくなる。
floatの指数部は最大127まで指定できる。しかし仮数部の整数部は1と決められているため、どう足掻いてもは表現できない。
よりも小さい整数が、float型で正確に表現できる最大の整数である。
指数表記に直すと3.40283466E+38で、C言語ではFLT_MAX
として定義されている。
小さな数字
大きな数字の次は小さな数字に着目したい。整数と違い、2進小数は10進小数をはなから正確に表現することができない。従って10進小数の0.1は、0.1に最も近い2進小数として表される。
手始めに整数部が1の2進小数を考えよう。このとき仮数部の暗黙の1はの位に紐づけられる。指数部を0に固定したと言ってもよい。
仮数部のbitが全て0ならば、二進小数は整数の1を表す。では、この次に大きい浮動小数点数は何だろうか。
結論を言うと、それは仮数部の最小bitだけが1の浮動小数点数である。
この数字は1よりもだけ大きい。10進数に直すと1.192E-7となる。この値は計算機イプシロンとよばれ、C言語ではFLT_EPSILON
として定義されている。
計算機イプシロンの説明として「1よりも大きな最小の値と1との差」と言われても正直な所、あまりピンと来ない。上に示したように、浮動小数点数の仮数部の最小bitを1大きくしたときの数字の変化量だと思えばよい。ただし実際の変化量は指数部によって大きくも小さくもなる。そこで、指数部が0のときの変化量を計算機イプシロンとして定義している。計算機イプシロンは誤差の議論をするときにまた詳しく説明したい。
一旦計算機イプシロンの話は忘れて、より小さな数字を探しに行こう。float型の指数部の最小値は-126なので、仮数部が全て0で指数部が-126の二進小数
が一番小さそうだ。これをは10進数で1.17549E-38であり、C言語ではFLT_MIN
として定義されている。
非正規化数
実は非正規化数を使うと、より小さな数字を表現できる。
とはいずれも同じ値である。整数部が1桁の指数表記にしたときに、整数部が0でない数字を正規化数と呼ぶ。反対に、整数部が0の数字を非正規化数という。
指数部の全てのbitが0のとき、浮動小数点数は非正規化数を表す。仮数部は「整数部を0とし、指数部をで固定した2進小数の小数部」である。ただし仮数部が0の場合は0を表す。
正規化数では0を表すことができない。言われてみればそうだなーと。
例えばはとなる。
0でない最小の非正規化数は
であり、が最小の非正規化数である。これは10進数で1.4012985E-45であり、C++11以降でFLT_TRUE_MIN
として定義されている。
まとめ
IEEE754の単精度浮動小数点のポイントとなる値をまとめておいた。
± | Exp | Exp(bits) | Mantissa(bits) | Math | Decimal | Meaning |
---|---|---|---|---|---|---|
0 | 127 | 11111110 | 11111111111111111111111 | 3.4028E+38 | FLT_MAX | |
0 | 23 | 10010110 | 11111111111111111111111 | 16777216-1 | Max # w/ 23 s.f. | |
0 | 0 | 01111111 | 00000000000000000000001 | 1.192E-07 | FLT_EPSILON | |
0 | -126 | 00000001 | 00000000000000000000000 | 1.17549E-38 | FLT_MIN | |
0 | -127 | 00000000 | 00000000000000000000001 | 1.40125E-45 | FLT_TRUE_MIN | |
0 | -127 | 00000000 | 00000000000000000000000 | 0 | 0 | Zero |
0 | -128 | 11111111 | 00000000000000000000000 | INFINITY | ||
0 | -128 | 11111111 | xxxxxxxxxxxxxxxxxxxxxxx | NAN | ||
1 | -128 | 11111111 | xxxxxxxxxxxxxxxxxxxxxxx | -NAN | ||
1 | -128 | 11111111 | 00000000000000000000000 | -INFINITY | ||
1 | -127 | 00000000 | 00000000000000000000000 | 0 | -0 | -Zero |
1 | -127 | 00000000 | 00000000000000000000001 | neg. of above | -1.40125E-45 | -FLT_TRUE_MIN |
1 | -126 | 00000001 | 00000000000000000000000 | neg. of above | -1.17549E-38 | -FLT_MIN |
1 | 127 | 11111110 | 11111111111111111111111 | neg. of above | -3.4028E+38 | -FLT_MAX |
参考
-
What Every Computer Scientist Should Know About Floating-Point Arithmetic
-
Wikipedia Links
-
Useful tools