6

アプリケーションを 32 ビットから 64 ビットに移植しています。現在、コードは両方のアーキテクチャでコンパイルされますが、結果は異なります。さまざまな理由から、double ではなく float を使用しています。float から double への暗黙的なアップコンバーティングが 1 つのマシンで発生し、他のマシンでは発生しないと想定しています。これを制御する方法、または私が探している特定の落とし穴はありますか?

編集して追加:

32 ビット プラットフォーム

 gcc (GCC) 4.1.2 20070925 (Red Hat 4.1.2-33)
 Dual-Core AMD Opteron(tm) Processor 2218 HE

64 ビット プラットフォーム

 gcc (Ubuntu 4.3.3-5ubuntu4) 4.3.3
 Intel(R) Xeon(R) CPU

-mfpmath=387 を適用すると、アルゴリズムを 1 回繰り返した後、値は同じになりますが、それを超えると再び同期しなくなります。

また、私の懸念は結果が同じではないということではなく、64 ビット プラットフォームへの移植によって、私が気付いていなかった 32 ビットの依存関係が明らかになったということです。

4

10 に答える 10

11

お使いのコンパイラは、おそらく SSE オペコードを使用して、x86-64 を想定した 64 ビット プラットフォームで浮動小数点演算のほとんどを実行していますが、互換性の理由から、以前は多くの操作で FPU を使用していた可能性があります。

SSE オペコードはより多くのレジスタと一貫性 (値は常に 32 ビットまたは 64 ビットのサイズのまま) を提供しますが、FPU は可能であれば 80 ビットの中間値を使用します。したがって、以前はこの改善された中間精度の恩恵を受けていた可能性が最も高いでしょう。(余分な精度は、計算が発生する距離に応じて、 x == y でも cos(x) != cos (y) のような一貫性のない結果を引き起こす可能性があることに注意してください!)

gcc でコンパイルしているため、64 ビット バージョンに -mfpmath=387 を使用して、結果が 32 ビットの結果と一致するかどうかを確認して、これを絞り込むことができます。

于 2009-07-02T19:24:20.163 に答える
8

float と double が 32 ビット コードと 64 ビット コードの間で異なる動作をする必要は本質的にありませんが、頻繁に発生します。あなたの質問に対する答えは、プラットフォームとコンパイラに固有のものになるため、移植元のプラットフォームと移植先のプラットフォームを指定する必要があります。

intel x86 プラットフォームでは、互換性を最大限にするために 32 ビット コードは x87 コプロセッサ命令セットと浮動小数点レジスタ スタックを使用することがよくありますが、amb64/x86_64 プラットフォームでは、SSE* 命令と xmm* レジスタが代わりに使用されることがよくあります。これらは異なる精度特性を持っています。

編集後:

プラットフォームによっては、x86_64 ビルドで -mfpmath=387 (i386 gcc のデフォルト) を試して、これが異なる結果を説明しているかどうかを確認することを検討してください。すべての -fmath-* コンパイラ スイッチの設定を調べて、両方のビルドで必要なものと一致することを確認することもできます。

于 2009-07-02T19:24:05.140 に答える
3

他の人が言ったように、あなたは何が起こっているのかを正確に伝えるのに十分な情報を提供していません. しかし、一般的な意味では、期待すべきではないある種の浮動小数点の動作を期待しているようです。

100 回中 99 回の問題は、どこかで 2 つの float が等しいかどうかを比較していることです。

問題が単にわずかに異なる答えを得ていることである場合は、どちらも「正しい」わけではないことを認識する必要があります。使用しているアーキテクチャに関係なく、何らかの丸めが行われます。計算の有効桁数を理解し、得られる値がある程度の近似値であることを認識しておくことが重要です。

于 2009-07-02T19:31:32.927 に答える
0

得るのが本当に難しいのは、両方の結果セットが正しいということです。変更を「異なる」以外のものとして特徴付けるのは公平ではありません。おそらく、古い結果に対する感情的な愛着が増しているのでしょう...しかし、64 ビットの結果よりも 32 ビットの結果を好む数学的な理由はありません。

このアプリケーションに固定小数点演算を使用する変更を検討しましたか? 固定小数点演算は、チップ、コンパイラ、およびライブラリが変更されても安定しているだけでなく、多くの場合、浮動小数点演算よりも高速です。

簡単なテストとして、バイナリを 32 ビット システムから 64 ビット システムに移動して実行します。次に、64 ビット システムでアプリを 32 ビット バイナリとして再構築し、それを実行します。これは、実際に発散動作を引き起こしている変更を特定するのに役立つ場合があります。

于 2009-07-02T20:17:58.153 に答える
0

すでに述べたように、両者が正しい限り、異なっていても問題はありません。理想的には、この種の単体テストを用意する必要があります (通常、純粋な計算は比較的テストしやすいキャンプに分類されます)。

基本的に、CPU とツールチェーン全体で同じ結果を保証することは不可能であり (1 つのコンパイラ フラグがすでに大幅に変更されている可能性があります)、一貫性を維持することはすでに非常に困難です。堅牢な浮動小数点コードを設計するのは難しい作業ですが、幸いなことに、多くの場合、精度は問題になりません。

于 2009-07-03T02:17:08.990 に答える
0

注意すべき重要な点の 1 つは、C 言語が最初に次のような計算を指定したことです。

float a=b+c+d;

b、c、および d を使用可能な最長の浮動小数点型 (たまたま type double) に変換し、それらを加算してから、結果を に変換しfloatます。このようなセマンティクスは、コンパイラにとっては単純であり、プログラマにとっては便利でしたが、少し問題がありました。数値を格納するための最も効率的な形式は、計算を実行するための最も効率的な形式と同じではありません。浮動小数点ハードウェアのないマシンでは、必ずしも正規化されていない 64 ビットの仮数として格納された値と、個別に格納された 15 ビットの指数および符号に対して計算を実行してから、64 ビットとして格納された値を操作する方が高速です。少しdoubleこれは、すべての操作の前にアンパックし、その後正規化して再パックする必要があります (次の操作のためにすぐにアンパックする場合でも)。マシンが中間結果をより長い形式で保持することで、速度と精度の両方が向上しました。ANSI C では type でこれが許可されていlong doubleます。

残念ながら、ANSI C は、可変引数関数が、すべての浮動小数点値を に変換するかlong double、すべて に変換するdoubleか、またはfloatととしてdouble渡して渡すdoubleかを示す手段を提供できませんでした。このような機能があれば、との値を区別する必要のないコードを簡単に作成できたはずです。残念ながら、そのような機能がないということは、システム上でlong doublelong doubledoublelong doubledoublelong double異なるタイプのコードは区別を気にする必要があり、そうでないシステムでは気にしません。これは、型が同じであるシステムで書かれた多くのコードが、そうでないシステムで壊れることを意味します。コンパイラ ベンダは、最も簡単な修正は単にlong doublebe と同義にしdouble、中間計算を正確に保持できる型を提供しないことであると判断しました。

中間計算を表現できない型で実行するのは悪いことなので、論理的なことは計算floatを type として実行することであると判断した人もいましたfloat。type を使用するよりも高速なハードウェア プラットフォームもありますがdouble、多くの場合、精度に望ましくない結果をもたらします。検討:

float triangleArea(float a, float b, float c)
{
  long double s = (a+b+c)/2.0;
  return sqrt((s-a)*(s-b)*(s-c)*c);
}

を使用して中間計算が実行されるシステムではlong double、これにより良好な精度が得られます。中間計算が として実行されるシステムでは、これにより、a、b、および c がすべて正確に表現できる場合でも、恐ろしいfloat精度が得られる可能性があります。たとえば、a と b が 16777215.0f で c が 4.0f の場合、 の値は16777217.0 になるはずですが、a、b、c の合計を として計算すると、1677216.0 になります。これにより、正しい値の半分未満の面積が得られます。a と c が 16777215.0f で b が 4.0f の場合 (同じ数値で順序が異なる)、16777218.0 として計算され、50% 大きすぎる領域が生成されます。sfloats

x86 では良い結果が得られる計算 (多くのコンパイラは、プログラマーには役に立たなくても 80 ビット型に積極的にプロモートします) があるが、x64 ではお粗末な結果になる場合は、次のような計算があると思います。これを超えると、オペランドまたは最終結果よりも高い精度で中間ステップを実行する必要があります。上記のメソッドの最初の行を次のように変更します。

  long double s = ((long double)a+b+c)/2.0;

低精度で計算を実行し、不正確な結果を高精度の変数に格納するのではなく、中間計算を高精度で実行するように強制します。

于 2015-04-03T23:13:28.777 に答える
0

gnu コンパイラには、状況によっては計算が中断される可能性がある浮動小数点数に関連する多くのコンパイラ オプションがあります。このページで「フロート」という用語を検索するだけで、それらを見つけることができます。

于 2009-07-02T19:28:08.377 に答える