浮動小数点数とデルタを比較して、線形連立方程式の解の結果をチェックする単体テストがいくつかあります。
Run test
デルタを調整しようとすると、同じ数値が Visual StudioとDebug test
モードの間でわずかに変化することに気付きました。
なぜこれが起こるのですか?テストをデバッグすると、#if DEBUG
セクションが無効になるため、実行されたコードは同じになるはずです。
ありがとう。
浮動小数点数とデルタを比較して、線形連立方程式の解の結果をチェックする単体テストがいくつかあります。
Run test
デルタを調整しようとすると、同じ数値が Visual StudioとDebug test
モードの間でわずかに変化することに気付きました。
なぜこれが起こるのですか?テストをデバッグすると、#if DEBUG
セクションが無効になるため、実行されたコードは同じになるはずです。
ありがとう。
典型的な DEBUG ビルドと RELEASE ビルド (最適化されていないものと最適化されているもの) の間で異なる結果を生成するコードの簡単な例については、LINQPadでこれを試してください。
void Main()
{
float a = 10.0f / 3;
float b = 10;
b /= 3;
(a == b).Dump();
(a - b).Dump();
}
最適化をオンにしてこれを実行すると (LINQPad ウィンドウの右下にある小さなボタンが "/o+" になっていることを確認してください)、次の結果が得られます。
False
-7,947286E-08
無効にして最適化をオフにすると、次のようになります。
True
0
生成された IL コードは同じであることに注意してください。
アドレスが異なることに注意してください。これは、純粋な IL 以外のものがあることを示している可能性がありますが、それが何であるかはわかりません。
浮動小数点計算に影響を与える可能性のあるあらゆる種類のものがありますが、その中で最も重要なのは、値を実際にローカル/フィールドに書き込むかどうかです。最適化されたビルドでは、JIT が値をレジスターに保持できる可能性があります。累積エラーを最小限に抑えるために、FPU レジスターは 80 ビット幅です。実際に値を 32 ビット ( float
) または 64 ビット ( double
) のローカルまたはフィールドに書き出す必要がある場合、必然的にその一部が失われます。そうです、レジスタですべての作業を行うことができれば、中間値をローカルなどに書き込む場合とは異なる (通常はより「正しい」) 結果を与えることができます。
他にも使用可能なレジスターがありますが、ここで使用されているとは思えません。XMM/SSE レジスターは 128 ビットです。SIMD は (マシンによって異なりますが) 512 ビットまで可能です。
ビルドを実行すると、完全な jit 最適化を使用して実行されます。実行時に、jit コンパイラーは巧妙なことを行います。
同じビルドをデバッグすると、jit の最適化がオフになります。したがって、異なるマシン コード命令が jit コンパイラによって生成されます。
最適化はさまざまです。一例として、変数の格納があります。変数はレジスタに格納されますが、すべてのレジスタが同じサイズであるとは限りません。コードが最適化されている場合、いくつかのステップが削除されるか、順番が入れ替わる場合があります。したがって、特定の操作に対するレジスタの選択は変わる可能性があります。そのため、保存値の精度が変化します。
これにより、浮動小数点計算の出力が異なります。
多くの場合、コンパイラは最小精度を保証しますが、中間ステップの最大精度を保証することはめったにありません。
CLR JIT 最適化は因果関係に違反していますか?も参照してください。
Visual Studio でさえ、異なる浮動小数点値Ctrl+F5
を生成します。F5
正確な値を出力する唯一の方法は、Release
モードで実行し、Visual Studio を使用せずにコードからテキスト ファイルを作成することです ( Ctrl+F5
)。異なるマシンは異なる浮動小数点値を生成するため、どこで生成するかはユーザー次第です。
このようにして、すべての浮動小数点数が正確に一致します!