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の方はなんだ。Phobosgrepしても出てこないので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で調べてからにすりゃ良かった。まあいいか、放置します。