web-dev-qa-db-ja.com

浮動小数点演算はC#で一貫していますか?できますか?

いいえ、これは別の "なぜ(1/3.0)* 3!= 1"質問ではありません。

私は最近、浮動小数点についてよく読んでいます。具体的には、同じ計算が、異なるアーキテクチャまたは最適化設定で異なる結果を与える可能性がある方法。

これは、リプレイを保存するビデオゲーム、または ピアツーピアネットワーク (サーバークライアントではなく)であり、実行するたびにすべてのクライアントがまったく同じ結果を生成することに依存するビデオゲームの問題です。プログラム-1つの浮動小数点計算のわずかな不一致により、異なるマシンで大幅に異なるゲーム状態が発生する可能性があります(または 同じマシンで!

これは、主に一部のプロセッサー(つまりx86)が 倍精度拡張 を使用するため、「従う」プロセッサー間でも発生します IEEE-754 。つまり、80ビットのレジスタを使用してすべての計算を実行し、64または32ビットに切り捨てて、64または32ビットを計算に使用するマシンとは異なる丸め結果を導きます。

私はこの問題のいくつかの解決策をオンラインで見ましたが、すべてC#ではなくC++に対応しています。

  • _controlfp_s (Windows)、_FPU_SETCW(Linux?)を使用して、倍精度拡張モードを無効にします(したがって、すべてのdouble計算でIEEE-754 64ビットが使用されます) 、または fpsetprec (BSD)。
  • 常に同じコンパイラーを同じ最適化設定で実行し、すべてのユーザーに同じCPUアーキテクチャーを使用する必要があります(クロスプラットフォームでのプレイはできません)。私の「コンパイラ」は実際にはJITであるため、プログラムを実行するたびに最適化される可能性があるため、これは不可能だと思います。
  • 固定小数点演算を使用し、floatdoubleを完全に避けます。 decimalはこの目的には機能しますが、はるかに遅くなり、System.Mathライブラリ関数はどれもサポートしません。

だから、これはC#の問題でもありますか?Windowsをサポートするつもりなら(Monoではなく)どうすればいいですか?

もしそうなら、プログラムを通常の倍精度で強制的に実行する方法はありますか?

そうでない場合、浮動小数点計算の一貫性を保つのに役立つライブラリがありますか

.netで通常の浮動小数点を確定的にする方法はありません。 JITterは、異なるプラットフォーム(または.netの異なるバージョン間)で異なる動作をするコードを作成できます。したがって、確定的な.netコードで通常のfloatsを使用することはできません。

私が検討した回避策:

  1. FixedPoint32をC#で実装します。これはそれほど難しいことではありませんが(実装が半分終わっています)、値の範囲が非常に狭いため、使用するのが面倒です。オーバーフローや精度の低下を防ぐため、常に注意する必要があります。最終的に、整数を直接使用するよりも簡単ではないことがわかりました。
  2. FixedPoint64をC#で実装します。これはかなり難しいと思いました。一部の操作では、128ビットの中間整数が有用です。しかし、.netはそのようなタイプを提供していません。
  3. カスタム32ビット浮動小数点を実装します。 BitScanReverse組み込み関数の欠如は、これを実装するときにいくつかの迷惑を引き起こします。しかし、現在、これが最も有望な道だと思います。
  4. 数学演算にはネイティブコードを使用します。すべての数学演算でデリゲート呼び出しのオーバーヘッドを引き起こします。

32ビット浮動小数点演算のソフトウェア実装を開始しました。 2.66GHz i3では、1秒あたり約7000万回の加算/乗算が可能です。 https://github.com/CodesInChaos/SoftFloat 明らかに、まだ非常に不完全でバグがあります。

52
CodesInChaos

C#仕様(§4.1.6浮動小数点型)では、結果の精度よりも高い精度を使用して浮動小数点計算を実行することが特に許可されています。ですから、.Netでこれらの計算を直接決定論的にすることはできないと思います。他の人はさまざまな回避策を提案したので、あなたはそれらを試すことができました。

27
svick

次のページは、このような操作の絶対的な移植性が必要な場合に役立ちます。浮動小数点演算をエミュレートするためのソフトウェアを含む、IEEE 754標準の実装をテストするためのソフトウェアについて説明します。ただし、ほとんどの情報はおそらくCまたはC++に固有のものです。

http://www.math.utah.edu/~beebe/software/ieee/

不動点に関する注記

4つの基本的な算術演算から明らかなように、2進固定小数点数は浮動小数点の代替としても機能します。

  • 加算と減算は簡単です。これらは整数と同じように機能します。加算または減算するだけです!
  • 2つの固定小数点数を乗算するには、2つの数値を乗算してから、定義された小数ビット数を右にシフトします。
  • 2つの固定小数点数を除算するには、被除数を定義された小数ビットの数だけ左にシフトし、除数で除算します。
  • このペーパー の第4章には、バイナリ固定小数点数の実装に関する追加のガイダンスがあります。

2進固定小数点数は、int、long、BigIntegerなどの整数データ型、および非CLS準拠の型uintおよびulongに実装できます。

別の回答で提案されているように、テーブル内の各要素がバイナリ固定小数点数であるルックアップテーブルを使用して、サイン、コサイン、平方根などの複雑な関数の実装を支援できます。ルックアップテーブルの粒度が固定小数点数よりも小さい場合、ルックアップテーブルの粒度の半分を入力に追加して入力を丸めることをお勧めします。

// Assume each number has a 12 bit fractional part. (1/4096)
// Each entry in the lookup table corresponds to a fixed point number
//  with an 8-bit fractional part (1/256)
input+=(1<<3); // Add 2^3 for rounding purposes
input>>=4; // Shift right by 4 (to get 8-bit fractional part)
// --- clamp or restrict input here --
// Look up value.
return lookupTable[input];
14
Peter O.

これはC#の問題ですか?

はい。異なるアーキテクチャは、あなたの心配、フレームレートなどの最も少ないものです-フロート表現の不正確さによる逸脱につながる可能性があります-それらがsame不正確さであっても(たとえば、1つのマシンでより遅いGPUを除いて、同じアーキテクチャ)。

System.Decimalを使用できますか?

できない理由はありませんが、犬は遅いです。

プログラムを強制的に倍精度で実行する方法はありますか?

はい。 CLRランタイムを自分でホストする ; CorBindToRuntimeExを呼び出す前に、すべてのnessecary呼び出し/フラグ(浮動小数点演算の動作を変更する)をC++アプリケーションにコンパイルします。

浮動小数点計算の一貫性を保つのに役立つライブラリはありますか?

私が知っていることではありません。

これを解決する別の方法はありますか?

以前にこの問題に取り組んだことがありますが、アイデアは QNumbers を使用することです。それらは固定小数点の実数の形式です。ただし、基数10(10進数)の固定小数点ではなく-基数2(バイナリ)。このため、それらの数学的プリミティブ(add、sub、mul、div)は、素朴なbase-10の固定小数点よりもはるかに高速です。特にnが両方の値で同じ場合(あなたの場合はそうです)。さらに、これらは統合されているため、すべてのプラットフォームで明確な結果が得られます。

フレームレートは依然としてこれらに影響を与える可能性がありますが、それほど悪くはなく、同期ポイントを使用して簡単に修正できます。

QNumbersでさらに数学関数を使用できますか?

はい、これを行うには小数を丸めます。さらに、trig(sin、cos)関数には lookup tables を実際に使用する必要があります。それらは本当に異なるプラットフォームで異なる結果を与えることができるので-それらを正しくコーディングすれば、QNumbersを直接使用できます。

9

この少し古い MSDNブログエントリ によると、JITは浮動小数点にSSE/SSE2を使用せず、すべてx87です。そのため、あなたが述べたように、モードとフラグについて心配する必要があり、C#では制御できません。したがって、通常の浮動小数点演算を使用しても、プログラムのすべてのマシンでまったく同じ結果が保証されるわけではありません。

倍精度の正確な再現性を得るには、ソフトウェアの浮動小数点(または固定小数点)エミュレーションを行う必要があります。これを行うC#ライブラリがわかりません。

必要な操作に応じて、単精度で脱出できる場合があります。アイデアは次のとおりです。

  • 関心のあるすべての値を単精度で保存する
  • 操作を実行するには:
    • 入力を倍精度に拡張する
    • 倍精度で操作を行う
    • 結果を単精度に戻す

X87の大きな問題は、精度フラグとレジスタがメモリにスピルしたかどうかに応じて、53ビットまたは64ビットの精度で計算が行われる可能性があることです。しかし、多くの操作では、操作を高精度で実行し、低精度に丸めることで正しい答えが保証されます。つまり、すべてのシステムで答えが同じであることが保証されます。どちらの場合でも正しい答えを保証するのに十分な精度があるため、余分な精度を取得するかどうかは関係ありません。

このスキームで動作するはずの操作:加算、減算、乗算、除算、sqrt。 sin、expなどのようなものは機能しません(結果は通常一致しますが、保証はありません)。 "二重丸めはいつ無害ですか?" ACMリファレンス(有料登録要求)

お役に立てれば!

6

他の回答ですでに述べたように:はい、これはC#の問題です-純粋なWindowsのままであっても。

解決策について:組み込みのBigIntegerクラスを使用し、すべての計算に共通の分母を使用してすべての計算を定義された精度にスケーリングすると、問題を完全に回避できますそのような番号の/ストレージ。

OPの要求どおり-パフォーマンスに関して:

System.Decimalは、1ビットの符号と96ビットの整数、および「スケール」(小数点の位置を表す)で数値を表します。あなたが行うすべての計算では、このデータ構造で動作する必要があり、CPUに組み込まれた浮動小数点命令を使用できません。

BigInteger "解決策"は似たようなことを行います-必要な/必要な桁数を定義できることだけです...たぶん80ビットまたは240ビットの精度だけが必要です。

CPU/FPUに組み込まれた命令を使用せずに整数のみの命令でこれらの数値のすべての演算をシミュレートする必要があるため、数学的な演算あたりの命令数が多くなります。

パフォーマンスへの影響を減らすには、QNumbers(Jonathan Dickinsonからの回答を参照してください- C#での浮動小数点演算は一貫していますか? )および/またはキャッシュ(例:トリガー計算) ..)など.

4
Yahia

さて、ここに私の最初の試みがありますこれを行う方法

  1. 重要な浮動小数点演算に使用する単純なオブジェクトを含むATL.dllプロジェクトを作成します。必ず、xx87以外のハードウェアを使用して浮動小数点を無効にするフラグを付けてコンパイルしてください。
  2. 浮動小数点演算を呼び出す関数を作成し、結果を返します。簡単に始めて、それがあなたのために働いているなら、あなたは後で必要ならあなたのパフォーマンスのニーズを満たすためにいつでも複雑さを増やすことができます.
  3. Control_fp呼び出しを実際の数学の周りに配置して、すべてのマシンで同じ方法で実行されるようにします。
  4. 新しいライブラリを参照し、テストして期待どおりに機能することを確認します。

(32ビットの.dllにコンパイルして、x86またはAnyCpuで使用できると信じています(または、64ビットシステムでx86のみをターゲットとする可能性があります。以下のコメントを参照してください)。)

次に、それが機能すると仮定して、Monoを使用したい場合、他のx86プラットフォームで同様の方法でライブラリを複製できるはずだと思います(もちろんCOMではありませんが、おそらくワインで?私たちはそこに行きます...)。

あなたがそれを機能させることができると仮定すると、パフォーマンスの問題を修正するために一度に複数の操作を行うことができるカスタム関数を設定することができ、最小限の量でプラットフォーム間で一貫した結果を得ることができる浮動小数点演算がありますC++で記述されたコードを使用し、残りのコードはC#のままにします。

2

私はゲーム開発者ではありませんが、計算が難しい問題については多くの経験がありますが...私はベストを尽くします。

私が採用する戦略は本質的にこれです:

  • より遅い(必要な場合、より速い方法があれば素晴らしい!)が、再現可能な結果を​​得るための予測可能な方法を使用する
  • 他のすべて(レンダリングなど)にdoubleを使用します

要するに、バランスを見つける必要があります。レンダリングに30ミリ秒(〜33 fps)を費やし、衝突検出に1ミリ秒しか費やしていない場合(または他の非常に敏感な操作を挿入する場合)-クリティカルな計算にかかる時間を3倍にしても、フレームレートに与える影響は33.3fpsから30.3fpsにドロップします。

すべてのプロファイルを作成し、著しく高価な計算の実行に費やす時間を考慮してから、この問題を解決する1つ以上の方法で測定を繰り返し、影響を確認することをお勧めします。

2

他の回答のリンクを確認すると、浮動小数点が「正しく」実装されているか、特定の計算で常に一定の精度を受け取るかどうかは保証されませんが、おそらく次の方法で最善の努力をすることができます。 (1)すべての計算を共通の最小値に切り捨てます(たとえば、異なる実装で32から80ビットの精度が得られる場合、すべての操作を常に30または31ビットに切り捨てます)、(2)起動時にいくつかのテストケースの表を作成します(加算、減算、乗算、除算、sqrt、コサインなどの境界線の場合)、および実装がテーブルに一致する値を計算する場合、調整は行われません。

1
mike