浮動小数点と誤差 のバックアップの現在との差分(No.1)


  • 追加された行はこの色です。
  • 削除された行はこの色です。
浮動小数点と誤差について

早速例を見てみよう
#code(cpp){{
	if(0.1+0.7==0.8){
		printf("0.1+0.7==0.8 当たり前!");
		printf("0.1+0.7==0.8 is true!");
	}else{
		printf("0.1+0.7==0.8 じゃない!?");
		printf("0.1+0.7==0.8 is false!?");
	}
}}
実行すれば分かるが、これは''偽''となる。(はず。少なくともこれを書いた人の環境ではそうなる)
値を変数に代入しても同様。

0.1等は2進数表記では循環小数となる。
そしてメモリは有限のためそれを有限桁で打ち切っている。
そしてメモリは有限のためそれを有限桁で打ち切っている。(最後を0捨1入で丸めている)
そこで誤差が発生しているのである。(これはC言語に限った問題ではない)

上の例でもわかるように、double型などについて==は使わない方が良い。

誤差にはほかにもいくつかの種類のものがある。
気が向いたときに追記する。

~
~
~
蛇足 見なくていいかもかも
・doubleの2進表現
環境依存かもしれないが次の方法で0.1がdoubleの中でどう表現されているか見てみよう
//この部分はもう少しいいプログラムないかなぁ
#code(cpp){{
	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
わずかにに異なるわけである。