8

次のコードを検討してください。

// Filename fputest.cpp

#include <cmath>
#include <cstdio>

int main()
{
    double x;
    *(__int64 *) &x = 0xc01448ec3aaa278di64; // -5.0712136427263319
    double sine1 = sin(x);
    printf("%016llX\n", sine1);
    double sine2;
    __asm {
    fld x
    fsin
    fstp sine2
    }
    printf("%016llX\n", sine2);
    return 0;
}

Visual C++ 2012 ( cl fputest.cpp) でコンパイルし、プログラムを実行すると、出力は次のようになります。

3FEDF640D8D36174
3FEDF640D8D36175

質問:

  • これら 2 つの値が異なるのはなぜですか?
  • 計算された正弦値がまったく同じになるように、いくつかのコンパイラ オプションを発行することは可能ですか?
4

4 に答える 4

9

この問題は、longdoubleからdoubleへの変換が原因ではありません。sin数学ライブラリのルーチンが不正確であることが原因である可能性があります。

命令は、fsinその範囲内のオペランドに対して1 ULP(long double形式)内の結果を生成するように指定されています(Intel 64およびIA-32アーキテクチャソフトウェア開発者マニュアル、2011年10月、第1巻、8.3.10による)。 to-nearestモード。Intel Core i7では、fsin質問者の値の-5.07121364272633190495298549649305641651153564453125または-0x1.448ec3aaa278dp+2は、0xe.fb206c69b0ba402p-4を生成します。この16進数から、最後の11ビットが100 0000 0010であることが簡単にわかります。これらは、longdoubleから変換するときに丸められるビットです。100 0000 0000より大きい場合、数値は切り上げられます。彼らはより大きいです。したがって、このlong double値をdoubleに変換した結果は、0xe.fb206c69b0ba8p-4になります。これは、0x1.df640d8d36175p-1および0.93631021832247418590355891865328885614871978759765625に等しくなります。また、結果が1 ULP低い場合でも、最後の11ビットは100 0000 0000より大きく、切り上げられることに注意してください。したがって、この結果は、上記のドキュメントに準拠するIntelCPUでは変化しないはずです。

sinこれを、正しく丸められた結果を生成する理想的なルーチンを使用して、倍精度の正弦を直接計算することと比較してください。値の正弦は、約0.93631021832247413051857150785044253634581268961333520518023697738674775240815140702992025520721336793516756640679315765619707343171517531053811196321335899848286682535203710849065933755262347468763562(Maple 10で計算)です。これに最も近いdoubleは0x1.df640d8d36175p-1です。fsinこれは、結果をdoubleに変換して得られた値と同じです。

したがって、不一致はlongdoubleからdoubleへの変換によって引き起こされるものではありません。long doubleのfsin結果をdoubleに変換すると、理想的な倍精度sinルーチンとまったく同じ結果が得られます。

sin質問者のVisualStudioパッケージで使用されるルーチンの精度に関する仕様はありません。商用ライブラリでは、1つのULPまたは複数のULPのエラーを許可するのが一般的です。正弦が倍精度値が丸められるポイントにどれだけ近いかを観察します。倍精度から.498864ULP(倍精度ULP)離れているため、丸めが変化するポイントから.001136ULP離れています。したがって、sinルーチンにごくわずかな不正確さがあったとしても、より近い0x1.df640d8d36175p-1ではなく0x1.df640d8d36174p-1が返されます。

したがって、不一致の原因はsinルーチンの非常に小さな不正確さであると私は推測します。

于 2012-09-01T12:23:22.073 に答える
1

x == yなのにcos(x)!= cos(y)なのはなぜですか?を参照してください。

Davidがコメントで述べたように、不一致はFPレジスタのデータを別のサイズのメモリ位置(レジスタ/ RAM)に移動することから生じます。そして、それは必ずしも割り当てではありません。別の近くの浮動小数点演算でさえ、FPレジスタをフラッシュするのに十分であり、特定の値を保証しようとする試みは無駄になります。比較を行う必要がある場合は、次のようにすべての結果をメモリ位置に強制することで、この一部を軽減できる可能性があります。

float F1 = sin(a); float F2 = sin(b); if (F1 == F2)

ただし、これでも機能しない場合があります。最良のアプローチは、浮動小数点演算は「ほとんど正確」であり、プログラマーの観点からは、同じ演算が繰り返し実行されたとしても、このエラーは事実上予測不可能でランダムであることを受け入れることです。それ以外の

if (F1 == F2)

あなたはの効果に何かを使用する必要があります

if (isArbitrarilyClose(F1, F2))

また

if (absf(F1 - F2) <= n)

n小さな数字です。

于 2012-09-01T07:56:52.590 に答える
1

(注: コメントで述べたように、これは VC2012 では機能しません。一般的な情報としてここに残しました。とにかく、最適化レベルに依存するものに依存することはお勧めしません!)

私はVS2012を持っていませんが、VS2010コンパイラ/fp:fastではコマンドラインで指定でき、同じ結果が得られます。これにより、コンパイラは、C++ で必要な丸めと規則に完全に従う必要はありませんが、アセンブリ言語の計算に一致する "高速" コードを生成します。

VS2012 でこれを試すことはできませんが、同じオプションがあると思います。

/Oxこれは、オプションとして最適化されたビルドでも機能するようです。

于 2012-09-01T07:24:07.160 に答える
0

VS2012のコードジェネレーターは、自動ベクトル化をサポートするように大幅に変更されました。その変更の一部は、x86浮動小数点演算がSSE2で実行され、FPUコードをベクトル化できないため、FPUを使用しなくなったことです。SSE2は、80ビット精度ではなく64ビット精度で計算するため、丸めのために結果が1ビットずれている可能性が高くなります。また、@J99がVS2010で/fp:fastと一貫した結果を得ることができる理由は、そのコンパイラーが引き続きFPUを使用し、/ fp:fastがFSIN結果を直接使用するためです。

この機能には多くの利点があります。リンクされたURLにあるJimHoggのビデオをチェックして、それを利用する方法を見つけてください。

于 2012-09-01T12:49:43.450 に答える