コンピュータが0.1を表現する場合の誤差について
(エクセルの演算誤差を説明したページ:とてもわかりやすい)
http://pc.nikkeibp.co.jp/pc21/special/gosa/eg4.shtml
IEEE(アイ・トリプル・イー)754:
米国電気電子技術者協会(The Institute of Electrical and Electronics
Engineers)。IEEE 754 は浮動小数点演算の規格。2進数を使うため、ほとんどの10進小数は正確に表現できない。にもかかわらずこの規格ををソフトウェアが採用するのは、ほとんどのPCがIEEE754規格の演算装置を内蔵しており、高速演算が可能なためである。
浮動小数点数 (floating-point number):
指数形式で表現した数値。例えば『1230』は『1.23E+03』と表示する。『1.23E+03』は見た目の小数点の位置と本当の小数点の位置が違っている。小数点位置が動くので、浮動小数点と呼ばれる。
IEEE 754 浮動小数点形式:
浮動小数点数では『-1.23×10の3乗』のような形で数を表現する。先頭の『-』を符号、『1.23』を仮数(かすう)、『10』を基数(きすう)、『3』を指数(しすう)と言う。つまり『(符号)(仮数)×(基数)の(指数)乗』で数を表現する。以下、規則。
- 基数は2。基数はデータには含めない。
- 符号は正を0、負を1とする
- 仮数は1以上、2未満にそろえる。この作業を正規化という。
- 0を表現するには、指数と仮数の全ビットを0にする。
- 仮数と指数は2進数とする
単精度浮動小数点形式(32ビット):
符号部:1ビット
指数部:8ビット(実際の指数に127を足す)
仮数部:23ビット(整数部分の1は省略する)
倍精度浮動小数点形式(64ビット):
符号部:1ビット
指数部:11ビット(実際の指数に1023を足す)
仮数部:52ビット(整数部分の1は省略する)
注)仮数部の整数部分の1が省略されるのは、仮数は必ず1以上2未満に正規化すると定められているので、表現する必要が無いからである。
10進数から2進数への変換:
ある実数を10進数から2進数に変換する場合、大きい2のべき乗(負のべき乗を含む)を引き続け、結果が正の数であるようにし、その場合のビットをたてる。それを0になるまで繰り返す。
例)3.25を2進数にするには:
3.25 - 4 = 3.25 – 2^2 = -0.75(不適)
3.25 - 2 = 3.25 – 2^1 = 1.25(適合) => 10
1.25 - 1 = 1.25 – 2^0 = 0.25(適合)=> 11
0.25 - 2^(-1) = 0.25 – 0.5 = -0.25(不適)=> 11.0
0.25 - 2^(-2) = 0.25 – 0.25 = 0(適合かつ完了)=> 11.01
2進数の倍精度形式への変換:
例1) 3.25(2進形式では、11.01)を倍精度形式にするには:
符号部:正なので0
仮数部:最も大きい桁を1以上2未満に。この場合1.101となる(11.01=1.101+E01の1.101)。
そこからさらに1を引き、0.101の内、小数点より右の101を格納する。
指数部:正規化の結果、指数は1となる。(11.01=1.101+E01のE01)
これに1023を加えて1024、つまり1000000000を格納する。
よって、
0 10000000000 1010000000|0000000000|0000000000|0000000000|0000000000|00
(符号部) (指数部) (仮数部)
例2) 0.1を倍精度形式にするには:
まず2進数に変換する。ここで我々は大きな問題に直面する。
0.1 – 2^(-3) = 0.1 - 0.125 = -0.025(不適)
0.1 - 2^(-4) = 0.1 - 0.0625 = 0.0375(適合) => 0.0001
0.0375 - 2^(-5) = 0.0375 - 0.01875 = 0.00625(適合)=> 0.00011
0.00625 - 2^(-6) = ? 0.00625 - 2^(-6) = -0.009375 (不適)0.000110
0.00625 - 2^(-7) = 0.00625 - 0.0078125 = -0.0015625(不適)=> 0.0001100
0.00625
- 2^(-8) = 0.00625 - 0.00390625 = 0.00234375(適合)=> 0.00011001
0.00234375 - 2^(-9) = 0.00234375 - 0.001953125 = 0.000390625(適合)=> 0.000110011
0.000390625 - 2^(-10) = 0.000390625 - 0.0009765625 = -0.0005859375(不適)=> 0.0001100110
0.000390625
- 2^(-11) = 0.000390625 - 0.00048828125 = -0.00009765625(不適)=> 0.00011001100
0.000390625
- 2^(-12) = 0.000390625 - 0.000244140625 = 0.000146484375 (適合)=> 0.000110011001
となり、どうやらこの数値は循環していて、永遠に割り切れそうに無い。しかし、倍精度形式の仮数部は52ビットしかないので、これでは結局、
符号部:正なので0
仮数部:0.000110011..を正規化し、1.10011..。
そこからさらに1を引き、0.10011..の内、小数点より右の10011..を格納する。
指数部:正規化の結果、指数は-4となる。(0.000110011..=1.10011..-E04)
これに1023を加えて1019、つまり01111111011を格納する。
よって、
0 01111111011 1001100110|0110011001|1001100110|0110011001|1001100110|01(10011..)
(符号部) (指数部) (仮数部) ↑
ここは入りきらない
となり、正確な値を格納することは出来ない。そこで、この入りきらない部分は、通常は「最近偶数丸め」という方法で丸める。
(最近偶数丸め):
基本的には0捨1入だが、端数が丁度1(=1.0)のときは、偶数になるように丸める。丸めた後の数字が丸める前の数字よりも全体として大きくなることを未然に防ぐ丸め方法。
これを用いると、110011..は1000000..に丸まる。よって、
0 01111111011 1001100110|0110011001|1001100110|0110011001|1001100110|10
(符号部) (指数部) (仮数部)
となり、これは2進数で、
+ -E04 1.1001100110011001100110011001100110011001100110011010
<=>
+ 0.00011001100110011001100110011001100110011001100110011010
であるので、10進数に直すと、
1/2^0*0+1/2^1*0+1/2^2*0+1/2^3*0+1/2^4*0+1/2^5*1+1/2^6*1+1/2^7*0+1/2^8*0+1/2^9*1+...
= 0.1000000000000000055511151231257827021181583404541015625
となり、我々が0.1だと思ってコンピュータに格納していた数値は、実は倍制度浮動小数点形式を用いた格納を採用しているコンピュータでは、0.0000000000000000055511151231257827021181583404541015625もの誤差を含んでいるのである。
これを未然に防ぐには、いわゆる「真数値形式」と呼ばれる格納方法を明示的に指示する必要がある。JavaではBigDecimal、SybaseではNumericなどのデータ型が定義されており、これらはいわゆる真数値形式である。