浮動小数点数の表現と誤差


はじめに

デジタル計算機におけるデータは、全て有限の bit によって表現されています。 ですから、 ある値を表現するために仮に N bit 使っているとするならば、 その値とは高々 2 N の状態しか取りえないわけです。
しかし、 数学における実数とは無限の値を取りうるものであり、 どんなに狭い区間だけを対象とすることにしても、 その区間内に無限個の値が存在するような代物です。

では、 そんな実数を、 高々数十とか百数十のビット数でどのように表現するのでしょうか。 そのことで、 どんな事態が起るのでしょうか。


浮動小数点形式

デジタル計算機を使って数値を表現する場合、 まず、 私たちは数値を『整数』と『実数』とに区別します。 デジタル計算機で用いる整数とは、 零を中心にプラス側とマイナス側にほぼ同じ拡がりを持つ変域を持ち、 その絶対値の最大値は整数表現に用いるビット数で決まります。

一方、 実数を表現するには、 『浮動小数点形式』と呼ばれる表現を用います。 ある値、 value を表現するために、多くの場合は

value = ( -1 )s * ( 1.f ) * 2e
という式で定義される三つの値の組を使います。 ここで、 s は『符号』と呼ばれる、 値が正か負かを示す 0 または 1 の値、 f は『仮数』と呼ばれる値で、 1 と小数点に続くことで 1 以上 2 未満の値を作り出すために用いられる値、 e は『指数』と呼ばれる値で、 value の絶対値のオーダーを示す、いわば値のスケールの大きさを示す値です。 こうして value 全体としては零を中心にプラス側とマイナス側に同じ幅を持つ変域をもちます。

しかし、このルールだけでは『零』を表現することができません。 そこで、『零』や特別に零に近い値を表現するためには例外的な約束を作り、 上記のルールに則って表現される数を『正規化数』、 例外的な表現をされる数を『非正規化数』と呼びます。 非正規化数については、 またあとで詳しく説明します。


いろいろな精度の浮動小数点

さて、 実際の浮動小数点数の表現がどうなっているかを見てみましょう。 私たちが使っている計算機が使っている浮動小数点数の表現形式の多くは IEEE Standard for Floating-Point Arithmetic (IEEE Std 754-2008) と呼ばれる規格にしたがったもので、 以下のような形式になっています。

単精度(短精度)浮動小数点形式

全部で 32 bit すなわち 4 byte を占める形式です。これを binary32 と呼ぶことがあります。

00010203040506070809 10111213141516171819 20212223242526272829 3031
s e+127 (8 bits) f (23 bits)

このように、 指数部を仮数部の左側に配置するのは、 大小比較を整数と同じ電子回路で行なえるようにするための工夫です。 同じ目的のために、指数を示す値は e の値に 127 という定数を加えたものにしています。 このように正の数と負の数とを、 その本当の値に定数を足した値で表現する方式を、 『下駄履き表示』方式と呼びます。

この形式では指数は 8 bit で、 その本当の値としては [-126,127] の範囲の値を表現でき、
仮数部の 23 bit で十進法でなら 7.22 桁の精度を持ちます。
正規化された数の絶対値の最大は ( 2 - 2-23 ) * 2127 = 3.4 * 1038
正規化された数の絶対値の最小は 2-126 = 1.2 * 10-38 となります。
また、 e=-127 は非正規化数を、 e=128 は計算の結果出てくる無限や数値でない数などを示します。

以下に幾つかの 32 bit の浮動小数点数のビットパターンを示します。

数値ビットパターン値の解釈
01234...89 10111213141516171819...31
1.0 0011111110000 0000 0000 0000 0000 000(-1)0 * ( 1.0000 0000 0000 0000 0000 000 BIN ) * 2(01111111 BIN - 127)
2.0 0100000000000 0000 0000 0000 0000 000(-1)0 * ( 1.0000 0000 0000 0000 0000 000 BIN ) * 2(10000000 BIN - 127)
-10.01100000100100 0000 0000 0000 0000 000(-1)1 * ( 1.0100 0000 0000 0000 0000 000 BIN ) * 2(10000010 BIN - 127)
100.00100001011001 0000 0000 0000 0000 000(-1)0 * ( 1.1001 0000 0000 0000 0000 000 BIN ) * 2(10000101 BIN - 127)
0.5 0011111100000 0000 0000 0000 0000 000(-1)0 * ( 1.0000 0000 0000 0000 0000 000 BIN ) * 2(01111110 BIN - 127)
0.25 0011111010000 0000 0000 0000 0000 000(-1)0 * ( 1.0000 0000 0000 0000 0000 000 BIN ) * 2(01111101 BIN - 127)
0.1250011111000000 0000 0000 0000 0000 000(-1)0 * ( 1.0000 0000 0000 0000 0000 000 BIN ) * 2(01111100 BIN - 127)
0.1 0011110111001 1001 1001 1001 1001 101(-1)0 * ( 1.1001 1001 1001 1001 1001 101 BIN ) * 2(01111011 BIN - 127)
ここで、 ???????? BIN という表現は、 ???????? を二進数としてみた場合の値という意味とします。

二進数に基づく浮動小数点表示では、 数値 0.125 が非常に切りの良い値になっている一方、 数値 0.1 が無限小数を丸めたものになっていることに注目してください。

倍精度(長精度)浮動小数点形式

全部で 64 bit すなわち 8 byte を占める形式です。これを binary64 と呼ぶことがあります。

0001020304050607 0809101112131415 1617181920212223 242526272829...63
s e+1023 (11 bits) f (52 bits)

この形式では指数は 11 bit で [-1022,1023] の範囲の値を表現でき、
仮数部の 52 bit で 十進法でなら 15.95 桁の精度を持ちます。
正規化された数の絶対値の最大は ( 2 - 2-52 ) * 21023 = 1.8 * 10308
正規化された数の絶対値の最小は 2-1022 = 2.2 * 10-308 となります。
また、 e=-1023 は非正規化数を、 e=1024 は計算の結果出てくる無限や数値でない数などを示します。

四倍精度(拡張倍精度)浮動小数点形式

全部で 128 bit すなわち 16 byte を占める形式です。これを binary128 と呼ぶことがあります。
なお、 プロセッサによっては必ずしも 128 bit ではなく、 64 bit を越える長さを持っていることをもって拡張倍精度としており、 コンパイラから四倍精度を指定したときにこのような形式が選択される場合があります。

00010203040506070809 10111213141516171819 20212223242526272829 ...127
s e+16383 (15 bits) f (112 bits)

この形式では指数は 15 bit で [-16382,16383] の範囲の値を表現でき、
仮数部の 112 bit で 十進法でなら 34.01 桁の精度を持ちます。
正規化された数の絶対値の最大は ( 2 - 2-112 ) * 216383 = 1.2 * 104932
正規化された数の絶対値の最小は 2-16382 = 3.4 * 10-4932 となります。
また、 e=-16383 は非正規化数を、 e=16384 は計算の結果出てくる無限や数値でない数などを示します。

拡張倍精度

拡張倍精度の例としては Intel のプロセッサを使っているシステムによくある 80 ビットからなる形式があります。 もともとは Intel の独自形式ですが、今ではかなりメジャーな業界標準になってしまっています。

00010203040506070809 10111213141516171819 20212223242526272829 ...80
s e+16383 (15 bits) f (64 bits)

この形式は指数の表現については四倍精度とおなじですが、 仮数部が 64 bit で 十進法でなら 19.26 桁の精度を持ちます。 なおこの形式では、 IEEE の表現方式とは異なり仮数部の整数部分の 1 をメモリ内に陽に保持することにしているので、 精度にさらに 1 ビット分の差が出ます。


非正規化数の表現

浮動小数点形式で『正規化数』を表現するには

value = ( -1 )s * ( 1.f ) * 2e
という式で定義される三つの値の組を使うことをお話ししましたが、 一方の非正規化数では、
value = ( -1 )s * ( 0.f ) * 2(-126 or -1022 or -16382)
という二つの値の組を使って表現します。

組を構成する要素の数がひとつ減ったのは、 指数を示すビット列が全て 0 になったときだけが、 非正規化数の出番だからです。 ちなみに、 2 の肩に乗る定数は、 上記の三つの精度に応じて順に -126 あるいは -1022 あるいは -16382 になります。

非正規化数の特別な場合である、 値『零』を示す場合には、 仮数を示すビット列も全て 0 になります。 また、浮動小数点表現でもっとも小さな絶対値を持つ正の値は非正規化数によって表現されて、 その値は上記の三つの精度に応じて順に 2-149, 2-1074, 2-16494 になります。


数直線上の浮動小数点数

さて、 延々と浮動小数点の表現方法を説明してきましたが、 連続な筈の実数をどのように飛び飛びの値しか取れない計算機上の値に割り当ててきたか、 イメージが掴めたでしょうか。 結構難しかったのではないかと思いますので、 もっと直感的な、 こんな例を考えてみました。

現実に使われることはあまりないと思いますが、 8 bit の浮動小数点形式を採用した計算機があったとします。 この機械の世界では、符号が 1 bit、 指数部が 3 bit で 3 の下駄履き表示、 仮数部が 4 bit で十進法で 1.2 桁の精度を持つものとします。

bit 0bit 1bit 2bit 3bit 4bit 5bit 6bit 7
s e+3 (3 bits) f (4 bits)

この形式では指数は 3 bit で、 その本当の値としては [-2,3] の範囲の値を表現でき、
正規化された数の絶対値の最大は ( 2 - 2-4 ) * 23 = 15.5 、
正規化された数の絶対値の最小は 2-2 = 0.25 、 非正規化数の絶対値の最小は 2-6 = 0.015625 となります。
また、 e=-3 は非正規化数を、 e=4 は計算の結果出てくる無限や数値でない数などを示します。

この形式では、 取りうるビットパターンは 256 通りですので、 その取りうる値も簡単に表にまとめて全部見てしまえる程度の個数にまで減ってしまいます。

上位
4bit
下位の 4 bit
0000000100100011010001010110011110001001101010111100110111101111
0000 0.000000 0.015625 0.031250 0.046875 0.062500 0.078125 0.093750 0.109375 0.125000 0.140625 0.156250 0.171875 0.187500 0.203125 0.218750 0.234375
0001 0.250000 0.265625 0.281250 0.296875 0.312500 0.328125 0.343750 0.359375 0.375000 0.390625 0.406250 0.421875 0.437500 0.453125 0.468750 0.484375
0010 0.500000 0.531250 0.562500 0.593750 0.625000 0.656250 0.687500 0.718750 0.750000 0.781250 0.812500 0.843750 0.875000 0.906250 0.937500 0.968750
0011 1.000000 1.062500 1.125000 1.187500 1.250000 1.312500 1.375000 1.437500 1.500000 1.562500 1.625000 1.687500 1.750000 1.812500 1.875000 1.937500
0100 2.000000 2.125000 2.250000 2.375000 2.500000 2.625000 2.750000 2.875000 3.000000 3.125000 3.250000 3.375000 3.500000 3.625000 3.750000 3.875000
0101 4.000000 4.250000 4.500000 4.750000 5.000000 5.250000 5.500000 5.750000 6.000000 6.250000 6.500000 6.750000 7.000000 7.250000 7.500000 7.750000
0110 8.000000 8.500000 9.000000 9.500000 10.00000 10.50000 11.00000 11.50000 12.00000 12.50000 13.00000 13.50000 14.00000 14.50000 15.00000 15.50000
0111 Inf NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1000 -0.000000 -0.015625 -0.031250 -0.046875 -0.062500 -0.078125 -0.093750 -0.109375 -0.125000 -0.140625 -0.156250 -0.171875 -0.187500 -0.203125 -0.218750 -0.234375
1001 -0.250000 -0.265625 -0.281250 -0.296875 -0.312500 -0.328125 -0.343750 -0.359375 -0.375000 -0.390625 -0.406250 -0.421875 -0.437500 -0.453125 -0.468750 -0.484375
1010 -0.500000 -0.531250 -0.562500 -0.593750 -0.625000 -0.656250 -0.687500 -0.718750 -0.750000 -0.781250 -0.812500 -0.843750 -0.875000 -0.906250 -0.937500 -0.968750
1011 -1.000000 -1.062500 -1.125000 -1.187500 -1.250000 -1.312500 -1.375000 -1.437500 -1.500000 -1.562500 -1.625000 -1.687500 -1.750000 -1.812500 -1.875000 -1.937500
1100 -2.000000 -2.125000 -2.250000 -2.375000 -2.500000 -2.625000 -2.750000 -2.875000 -3.000000 -3.125000 -3.250000 -3.375000 -3.500000 -3.625000 -3.750000 -3.875000
1101 -4.000000 -4.250000 -4.500000 -4.750000 -5.000000 -5.250000 -5.500000 -5.750000 -6.000000 -6.250000 -6.500000 -6.750000 -7.000000 -7.250000 -7.500000 -7.750000
1110 -8.000000 -8.500000 -9.000000 -9.500000 -10.00000 -10.50000 -11.00000 -11.50000 -12.00000 -12.50000 -13.00000 -13.50000 -14.00000 -14.50000 -15.00000 -15.50000
1111 -Inf -NaN -NaN -NaN -NaN -NaN -NaN -NaN -NaN -NaN -NaN -NaN -NaN -NaN -NaN -NaN

表を見てみてください。 上半分が正の数、下の半分が負の数です。
背景色が黄色になっているところが『非正規化数』のあるところです。 背景色が赤くなっているところは『数でないモノ』を表しているビットパターンで、 仮数部がすべて 0 の場合は Inf で『無限大 (Infinity) 』を、 仮数部が 0 以外の場合は NaN で『数でないモノ (Not a Number)』を意味しています。 計算結果を印字させたときに、 Inf や NaN といった文字列が出力されることがありますが、 これはこういう値です。

『零』は、正の方にも、負の方にもあります。 数学的な値としてはひとつなのですが、 表現に用いるビットパターンは二種類あるので、 この表にも二回出てきます。
横に並んでいる各行に含まれる数は全て等間隔に並んでいます。 しかし、 その間隔は行ごとに異なっています。 たとえば、 2 の次の数は 2.125 ですが、 その直前の数は 1.9375 で、 ある値とその零よりの隣人との間隔の方が、 その反対側の隣人との間隔より狭くなっているところもあります。
浮動小数点表現では、 有効数字の桁数を一定に保つことを目論んでいるので、 絶対値が小さくなるとその分だけ、 隣接した値と値の間隔が狭くなります。


浮動小数点数を用いた計算と誤差の発生

では、 このような表現を採用している計算機を使って数値計算をしたとすると、 どんな事が起るでしょうか。 たとえば、 4.000000 + 0.296875 を計算したとすると、 数学の世界でのその答えは 4.296875 になります。 しかし、 上の表を良く見ると、 4.250000 の次の数はもうすぐに 4.500000 になっていて、 そのあいだの数値を表現することはできないことがわかります。
すなわち、 この8ビット浮動小数点形式を使っている計算機にとっては、 4.000000 + 0.296875 = 4.250000 であり、これがこの計算機にとって精一杯正確な計算なのです。
また、 このように数学の世界での正確な答えに一番近い数を探してくるという『丸め』操作を、 よほど運が良くない限り、 何らかの演算を行なうたびに行なうことになります。 演算を一回行なう度に、 その答えに『丸め誤差』を混ぜ込んでいるのです。

もうひとつ、 非常に極端な例をあげましょう。 9.500000 + 0.078125 は、 数学の世界では 9.578125 ですが、 この計算機の世界では 9.500000 の次はもう 10.00000 ですから、 9.578125 はどちらかというと 9.500000 により近いことがわかります。 ですから、 この計算機の世界では、 9.500000 + 0.078125 = 9.500000 となります。
さて、 一体何が起ってしまったのでしょう。 この計算機にとっては A + B = A という等式が成り立ってしまったわけです。 B = 0.078125 と B = 0 とが同時に成立しているこの状況は、 普通なら矛盾と呼ばれる状況で、 『情報落ち』と呼ばれるものです。

このような事態は、 ビット数が少ないために起ったのではありません。 どれだけ多くのビット数を割り当てても、 確かにそれで回避できる確率は高くはなるのですが、 多かれ少なかれ起ってしまうことは避けられません。 数値計算では充分注意していないと、 このようなことがいくらでも発生する可能性があります。
浮動小数点表示という方式は、 本質的に一つの数で数直線上のある幅を持った領域を代表するものであって、 数直線上の一点を示すものではありません。 そのことに十分ご注意下さい。 ( 一方、整数は数直線上の一点のみを示すもので、そこに曖昧さはありません。 )

さらに付け加えるならば、 計算の誤差は入出力の場合にも発生します。 入出力においては、 二進数と十進数との間の変換を 数値計算を用いて行ないます。 ですから、 この段階で当然、 計算誤差が入りますし、 場合によっては変換結果が無限小数になってしまうために丸め誤差も入ります。
ですから、 人間がデータを与えるときには、 それがプログラムに入力から与えられようと、プログラム中に定数として記述されていようと、 何の計算もしないうちから誤差を含んでいる可能性があります。 データ表現の違いによって演算結果がどのように変化するかを知りたいときには、 入出力の直前直後だけでも可能な限り高い精度のデータ表現を用いるなどの配慮が必要です。


浮動小数点数の演算に纏わるコスト

今までの議論では、 少しでも精度の高いデータ表現を使えば良いような印象を持ちますが、 それは少し違います。 確かに精度だけを考えればそうなのですが、 その代わりに失っているものもあります。

まず、 精度の高いデータ表現を使って処理をしようとすると、 より多くの演算時間を必要とするようになります。 また、 必要なデータを計算機上に保持するために、 より多くの記憶空間を必要とするようになります。 これらの問題は、 行なおうとしている演算処理が大規模なものになればなるほど、 顕著になります。

たとえば、 演算時間の問題を例とするなら、 最近のプロセッサは単精度と倍精度浮動小数点形式をとるデータに対しては 専用の演算回路を用意しておいて処理するものが大多数ですが、 四倍精度浮動小数点形式をとるデータについては ソフトウェアで処理するものが多数あります。 このようなプロセッサを使った計算機では 四倍精度浮動小数点形式は 本当に必要なところでしか使わない方が良いことにもなりかねません。 また、 倍精度浮動小数点形式をとるデータについても、 プロセッサに用意された専用の演算回路で処理されるとしても、 その処理速度は単精度浮動小数点形式用の回路を使う計算の数倍の時間がかかるのが普通です。 ですから、 あまり考えずに単精度の代わりに倍精度浮動小数点形式を使うと、 一日二日で終わるはずの計算が 一週間掛かっても終わらない、 といった羽目に陥ることになります。

一方、 記憶空間の問題を例にとれば、 倍精度浮動小数点形式をとるデータは 単精度浮動小数点形式をとるデータの倍の記憶空間を必要とします。 また、 四倍精度浮動小数点形式をとるデータは、 さらにその倍の記憶空間を必要とします。 この差は大規模な配列を処理する場合などに効いてきて、 たとえば特定の計算機で計算可能なマトリックスのサイズなどを決められてしまうことになります。
演算時間の問題は、 時間は掛かりますが 同じプログラムでも 待っていれば何時かは答えが出てきます。 しかし、 記憶空間の問題は 別のアルゴリズムでプログラムを書き換えるか、 あるいは 別の計算機を探して来ない限り解決しませんので、 頭の痛い問題です。


おまけ: 浮動小数点数表現の比較

単精度 倍精度 拡張倍精度 四倍精度 参考: 8 bit 精度
総バイト数 4 8 10 16 1
総ビット数 32 64 80 128 8
仮数部ビット数 23 52 64 112 4
指数部ビット数 8 11 15 15 3
仮数部の有効数字桁数 7.22 15.95 19.26 34.02 1.51
指数部の表現範囲 [-126,127] [-1022,1023] [-16382,16383] [-16382,16383] [-2,3]
指数部の下駄の高さ 127 1023 16383 16383 3
EPSILON 1.19 * 10-72.22 * 10-161.08 * 10-19 1.93 * 10-34 0.0625
正規化数の絶対値の最大 3.4 * 1038 1.8 * 10308 1.2 * 104932 1.2 * 104932 15.5
正規化数の絶対値の最小 1.2 * 10-382.2 * 10-3083.4 * 10-49323.4 * 10-49320.25
非正規化数の絶対値の最小1.4 * 10-454.9 * 10-3243.6 * 10-49516.5 * 10-49660.015625


takata@cc.uec.ac.jp