47

私は.NETの浮動小数点決定論について多くのことを読んでいます。つまり、同じ入力を持つ同じコードが異なるマシン間で同じ結果をもたらすことを保証します。.NETにはJavaのfpstrictやMSVCのfp:strictなどのオプションがないため、純粋なマネージコードを使用してこの問題を回避する方法はないというのがコンセンサスのようです。C#ゲームのAI Warsは、代わりに固定小数点演算を使用することに決めましたが、これは面倒な解決策です。

主な問題は、CLRにより、中間結果がタイプのネイティブ精度よりも高い精度のFPUレジスタに存在できるようになり、予想外に高い精度の結果が得られることです。CLRエンジニアのDavidNotarioによるMSDNの記事では、次のように説明しています。

現在の仕様では、「予測可能性」を与えることは依然として言語の選択であることに注意してください。言語は、すべてのFP操作の後にconv.r4またはconv.r8命令を挿入して、「予測可能な」動作を取得する場合があります。 明らかに、これは本当に高価であり、言語が異なれば妥協点も異なります。たとえば、C#は何もしません。絞り込みが必要な場合は、(フロート)キャストと(ダブル)キャストを手動で挿入する必要があります。

これは、浮動小数点と評価されるすべての式と部分式に明示的なキャストを挿入するだけで、浮動小数点の決定論を実現できることを示しています。このタスクを自動化するために、floatの周りにラッパータイプを書くかもしれません。これはシンプルで理想的なソリューションです!

しかし、他のコメントは、それがそれほど単純ではないことを示唆しています。Eric Lippertは最近述べました(私の強調):

ランタイムの一部のバージョンでは、floatにキャストすると、明示的にキャストしない場合とは異なる結果が得られます。明示的にfloatにキャストすると、C#コンパイラはランタイムに「この最適化を使用している場合は、これを超高精度モードから外してください」というヒントを提供します。

ランタイムへのこの「ヒント」は何ですか?C#仕様では、floatへの明示的なキャストにより、ILにconv.r4が挿入されると規定されていますか?CLR仕様では、conv.r4命令によって値がネイティブサイズに絞り込まれることが規定されていますか?これらの両方が当てはまる場合にのみ、David Notarioによって説明されているように、浮動小数点の「予測可能性」を提供するために明示的なキャストに依存できます。

最後に、実際にすべての中間結果をタイプのネイティブサイズに強制できる場合でも、これはマシン間の再現性を保証するのに十分ですか、それともFPU / SSEランタイム設定などの他の要因がありますか?

4

2 に答える 2

28

ランタイムへのこの「ヒント」は何ですか?

ご想像のとおり、コンパイラは、doubleまたはfloatへの変換が実際にソースコードに存在するかどうかを追跡し、存在する場合は、常に適切なconvオペコードを挿入します。

C#仕様では、floatへの明示的なキャストにより、ILにconv.r4が挿入されると規定されていますか?

いいえ。ただし、コンパイラのテストケースには、それが確実に行われる単体テストがあることを保証します。仕様はそれを要求していませんが、あなたはこの振る舞いに頼ることができます。

仕様の唯一のコメントは、浮動小数点演算は実行時の気まぐれで必要とされるよりも高い精度で実行される可能性があり、これにより結果が予想外により正確になる可能性があるということです。セクション4.1.6を参照してください。

CLR仕様では、conv.r4命令によって値がネイティブサイズに絞り込まれることが規定されていますか?

はい、パーティションIのセクション12.1.3で、インターネットに依頼するのではなく、自分で調べた可能性があることに注意してください。これらの仕様はWeb上で無料です。

あなたが尋ねなかったが、おそらく持っているべき質問:

高精度モードからフロートを切り捨てるキャスト以外の操作はありますか?

はい。静的フィールド、インスタンスフィールド、double[]またはまたはfloat[]配列の要素への割り当ては切り捨てられます。

一貫した切り捨ては、マシン間の再現性を保証するのに十分ですか?

いいえ。非正規化数とNaNについては、非常に興味深いセクション12.1.3をお読みになることをお勧めします。

そして最後に、あなたが尋ねなかったがおそらく持っているべき別の質問:

再現性のある演算を保証するにはどうすればよいですか?

整数を使用します。

于 2013-02-13T22:56:39.877 に答える
25

8087浮動小数点ユニットのチップ設計は、Intelの10億ドルの間違いでした。このアイデアは紙の上では見栄えがよく、拡張精度の80ビットで値を格納する8レジスタスタックを提供します。中間値が有効数字を失う可能性が低い計算を記述できるようにします。

しかし、獣は最適化することは不可能です。FPUスタックからメモリに値を保存するのはコストがかかります。したがって、それらをFPU内に保持することは、強力な最適化の目標です。必然的に、計算が十分に深い場合、レジスタが8つしかない場合は、ライトバックが必要になります。また、スタックとして実装されており、自由にアドレス指定できるレジスタではないため、体操も必要であり、書き戻しが発生する可能性があります。必然的に、ライトバックは値を80ビットから64ビットに切り捨て、精度を低下させます。

したがって、結果として、最適化されていないコードは、最適化されたコードと同じ結果を生成しません。また、中間値を書き戻す必要が生じた場合、計算に小さな変更を加えると、結果に大きな影響を与える可能性があります。/ fp:strictオプションはその周りのハックであり、値の一貫性を維持するためにコードジェネレーターにライトバックを発行させますが、パフォーマンスの低下は避けられません。

これは完全な岩であり、難しい場所です。x86ジッターの場合、彼らは問題に対処しようとしませんでした。

Intelは、SSE命令セットを設計したときに同じ間違いを犯しませんでした。XMMレジスタは自由にアドレス指定でき、余分なビットを格納しません。一貫した結果が必要な場合は、AnyCPUターゲットと64ビットオペレーティングシステムを使用してコンパイルするのが迅速なソリューションです。x64ジッタは、浮動小数点演算にFPU命令の代わりにSSEを使用します。これにより、計算で異なる結果が得られる3番目の方法が追加されました。有効桁数が多すぎるために計算が間違っている場合は、一貫して間違っています。これは実際には少し臭化物ですが、通常はプログラマーが見る限りです。

于 2013-02-13T23:34:01.673 に答える