浮動小数点と誤差
浮動小数点と誤差について
早速例を見てみよう
if(0.1+0.7==0.8){ printf("0.1+0.7==0.8 is true!"); }else{ printf("0.1+0.7==0.8 is false!?"); }
実行すれば分かるが、これは偽となる。(はず。少なくともこれを書いた人の環境ではそうなる)
値を変数に代入しても同様。
0.1等は2進数表記では循環小数となる。
そしてメモリは有限のためそれを有限桁で打ち切っている。(最後を0捨1入で丸めている)
そこで誤差が発生しているのである。(これはC言語に限った問題ではない)
上の例でもわかるように、double型などについて==は使わない方が良い。
誤差にはほかにもいくつかの種類のものがある。
気が向いたときに追記する。
蛇足 見なくていいかもかも
・doubleの2進表現
環境依存かもしれないが次の方法で0.1がdoubleの中でどう表現されているか見てみよう
union{ unsigned long long int intnum; double doublenum; }num; num.doublenum = 0.1; for(int i=0;i<64;++i){ printf("%d",num.intnum>>63); num.intnum<<=1; }
書いた人の環境では次のように表示される。
0011111110111001100110011001100110011001100110011001100110011010
これを見ても何がなにやらさっぱりであろう。
double型は符号部1ビット、指数部11ビット、仮数部52ビットで表現される。
上の値をそれに応じて区切ると次のようになる
0 01111111011 1001100110011001100110011001100110011001100110011010
左から符号部、指数部、仮数部である。
これが次のように解釈される。(以下a^bによってaのb乗を表す)
(-1)^符号部×2^(指数部-1023)×(1+仮数部)
符号部は0なら+、1なら-である。
指数部から1023を引いているのは指数部が1023足された表現がされているためである。
仮数部は小数点以下の部分を表わしている。(1.仮数部 のようになる)
例えば
0.75=0.11=(-1)^0×2^(-1)×(1+0.5)
なので符号部は0、指数部は1022(=-1+1023=01111111110)、仮数部は0.5(=1000000000000000000000000000000000000000000000000000)
よって0.75をdoubleで表すと
0011111111101000000000000000000000000000000000000000000000000000
となる。
では、0.1はどうだろう?
0.1を2進数で表そうとすると、
0.1=0.000110011...
となって0011が無限に続く循環小数になる。
しかし仮数部は52ビットしかない。ここで丸められることになる。
実際doubleでどう表されるか考えよう。
符号部が0なのは良いだろう。
指数部は、仮数部が1+仮数部の形で使われるため、適度に数をずらすことになる。
つまり、0.000110011... => 1.100110011... × 2^(指数部-1023)
となるようにする。今回は2^(-4)でなので指数部は1019(=01111111011)
仮数部は1.100110011...の小数部分なので左から53ビット目まで表示すると
10011001100110011001100110011001100110011001100110011...
左から53ビット目で0捨1入が行われる。左から53ビット目が1であるので、左から53ビット目以降を捨てて、左から52ビット目に1を足すと
1001100110011001100110011001100110011001100110011010
これらをつなげると
0011111110111001100110011001100110011001100110011001100110011010
となる。正にプログラムで表示したものと一致したのである。
また、丸められていることも理解してもらえたと思う。
ちなみに、0.7は
0011111111100110011001100110011001100110011001100110011001100110
0.1+0.7は
0011111111101001100110011001100110011001100110011001100110011001
0.8は
0011111111101001100110011001100110011001100110011001100110011010
わずかにに異なるわけである。