8

このプログラムの出力を理解するのに問題があります

int main()
{
    double x = 1.8939201459282359e-308;
    double y = 4.9406564584124654e-324;
    printf("%23.16e\n", 1.6*y);
    printf("%23.16e\n", 1.7*y);
    printf("%23.16e\n", 1.8*y);
    printf("%23.16e\n", 1.9*y);
    printf("%23.16e\n", 2.0*y);
    printf("%23.16e\n", x + 1.6*y);
    printf("%23.16e\n", x + 1.7*y);
    printf("%23.16e\n", x + 1.8*y);
    printf("%23.16e\n", x + 1.9*y);
    printf("%23.16e\n", x + 2.0*y);
}

出力は

9.8813129168249309e-324
9.8813129168249309e-324
9.8813129168249309e-324
9.8813129168249309e-324
9.8813129168249309e-324
1.8939201459282364e-308
1.8939201459282364e-308
1.8939201459282369e-308
1.8939201459282369e-308
1.8939201459282369e-308

IEEE演算を使用しています。変数yは、可能な限り最小のIEEE番号を保持します。最初の5つのプリントは、私が予想する2倍のyの数を示しています。私を混乱させているのは、次の5つのプリントが異なる数字を示していることです。1.6*yが同じである場合2.0*y、どのようx + 1.6*yに異なることができますx + 2.0*yか?

4

1 に答える 1

8

一言で言えば

コンパイラはVisualC++2010Expressであるとあなたは言います。私はこのコンパイラにアクセスできませんが、IEEE 754倍精度計算を可能な限りエミュレートするために、53ビットの精度を使用するようにx87CPUを最初に構成するプログラムを生成することを理解しています。

残念ながら、「できるだけ近く」は必ずしも十分に近いとは限りません。過去の80ビット浮動小数点レジスタは、倍精度をエミュレートする目的で幅を大幅に制限できますが、指数の全範囲を常に保持します。違いは、特に非正規化数(のようにy)を操作するときに示されます。

何が起こるのですか

私の説明ではprintf("%23.16e\n", 1.6*y);1.6*yは、80ビットの仮数と全指数の数値(したがって正規数)として計算され、IEEE 754倍精度に変換されて(非正規化数になります)、印刷されます。

一方、inはprintf("%23.16e\n", x + 1.6*y);x + 1.6*yすべての80ビットの仮数と全指数の数値(ここでもすべての中間結果は正規数)を使用して計算され、IEEE754倍精度に変換されてから印刷されます。

1.6*yこれは、と同じように印刷する2.0*yが、に追加すると異なる効果を持つ理由を説明しxます。印刷される数値は倍精度の非正規化数です。に追加される数値xは、80ビットの仮数と全指数の通常の数値です(同じ数値ではありません)。

x87命令を生成するときに他のコンパイラで何が起こるか

GCCなどの他のコンパイラは、53ビットの仮数を操作するようにx87FPUを構成しません。これは同じ結果をもたらす可能性があります(この場合x + 1.6*y、すべての80ビットの完全な仮数と完全な指数で計算され、印刷またはメモリに格納するために倍精度に変換されます)。この場合、問題はさらに頻繁に顕著になります(違いに気付くために非正規化数や無限数を含める必要はありません)。

David Monniauxによるこの記事には、あなたが望むかもしれないすべての詳細などが含まれています。

不要な動作を削除する

問題を取り除くには(問題があると思われる場合)、浮動小数点用のSSE2命令を生成するようにコンパイラーに指示するフラグを見つけます。これらは、単精度および倍精度のIEEE754セマンティクスを正確に実装します。

于 2013-03-15T20:36:51.943 に答える