0.1を10回足しても1にならない@拡張精度
有名な話なんですが、じゃあ実際どれぐらい誤差が出るのよと。案外doubleまでしか検証されて無いんじゃないかと思ってやってみました。
program Project1; {$APPTYPE CONSOLE} uses Crt; var F: Single = 0; D: Double = 0; E: Extended = 0; I: Integer; begin for I := 1 to 10 do begin F := F + 0.1; D := D + 0.1; E := E + 0.1; end; WriteLn(F - 1); WriteLn(D - 1); WriteLn(E - 1); end.
1.19209289550781E-0007 -1.11022302462516E-0016 1.08420217248550E-0019
拡張精度にしても3桁ちょっとしか差が付いてないですが、倍精度と拡張精度の仮数部の差は12bitしかないので、log 4096 / log 10 = 3.61..でこんなものか。
表示の際-1して誤差だけ見てますが、これ実はそのまま表示すると1になります。表示の際四捨五入してんのね。
Sqrt(2) * Sqrt(2)なんかもほぼ同じ。
検証のためDでもやってみた。
import std.stdio; void main() { float f = 0; double d = 0; real e = 0; for(int i = 0; i < 10; ++i){ f += 0.1; d += 0.1; e += 0.1; } writefln(f - 1.0); writefln(d - 1.0); writefln(e - 1.0); }
1.19209e-07 -1.11022e-16 5.55112e-17
……結果違くね?
表示桁数の違いはいいとして、Dの方が拡張精度での誤差が大きいから……FPUの設定間違えてるだろWalterさん。(結局ここに行き着くのかよ)
DelphiのDefault8087CWが$1332で、と。Dの方はなんだ。Phobosをgrepしても出てこないのでasmしかないか。
その前に……私最後にdmd入れたの0.130かよ……まあ変わらないだろうけど最新版0.167に更新と。
1.19209e-07 -1.11022e-16 1.0842e-19
……直ってやがる。
直っている以上意味がないのですが、インラインアセンブラの構文に苦労した(レジスタが大文字でないといけないのがわからずoffset付けたりdword ptr付けたり)のでコントロールワードを調べる例。
ushort x; asm{ lea EAX, x; fstcw [EAX]; } writefln("%.4x", x);
結果は0x137fでした。
しまった、一旦0.130で調べてからにすりゃ良かった。まあいいか、放置します。