web-dev-qa-db-ja.com

サイン結果は使用するC ++コンパイラに依存します

次の2つのC++コンパイラを使用します。

  • cl.exe:Microsoft(R)C/C++ Optimizing Compiler Version 19.00.2421 for x86
  • g ++:g ++(Ubuntu 5.2.1-22ubuntu2)5.2.1 20151010

組み込みサイン関数を使用すると、異なる結果が得られます。これは重要ではありませんが、結果は私の使用にはあまりにも重要です。 「ハードコードされた」値の例を次に示します。

printf("%f\n", sin(5451939907183506432.0));

cl.exeでの結果:

0.528463

g ++での結果:

0.522491

私はg ++の結果がより正確であり、追加のライブラリを使用して同じ結果を得ることができることを知っていますが、それはここでの私のポイントではありません。ここで何が起こるか本当に理解できます:なぜcl.exeが間違っているのですか?

おかしなことに、パラメータに(2 * pi)のモジュロを適用すると、g ++と同じ結果が得られます...

[編集]私の例が一部の人にとってクレイジーに見えるという理由だけで:これは擬似乱数ジェネレータの一部です。サインの結果が正確であるかどうかを知ることは重要ではありません。何らかの結果を出すために必要なだけです。

37
Nicolas

サムのコメントはマークに最も近いと思います。ソフトウェアでsin()を実装するGCC/glibcの最近のバージョン(問題のリテラルのコンパイル時に計算される)を使用しているのに対し、x86のcl.exeはfsin命令を使用する可能性があります。 Random ASCII blog post、 " Intel Underestimates Error Bounds by 1.3 quintillion "で説明されているように、後者は非常に不正確な場合があります。

特にあなたの例の問題の一部は、範囲縮小を行うときにIntelがpiの不正確な近似を使用することです:

倍精度(53ビット仮数)piから範囲縮小を行うと、結果は約13ビットの精度(66から53)になります。最大2 ^ 40 ULPのエラー(53-13)。

16
SloopJon

19桁のリテラルがありますが、doubleは通常15〜17桁の精度です。その結果、小さな相対誤差(doubleに変換する場合)を得ることができますが、十分な(正弦計算のコンテキストで)絶対誤差を得ることができます。

実際、標準ライブラリの実装が異なると、そのような多数の処理に違いがあります。たとえば、私の環境では、実行する場合

_std::cout << std::fixed << 5451939907183506432.0;
_

g ++の結果は_5451939907183506432.000000_になります
clの結果は_5451939907183506400.000000_になります

違いは、19より前のバージョンのclには、限られた桁数だけを使用し、残りの小数点以下桁数をゼロで埋めるフォーマットアルゴリズムがあるためです。

さらに、このコードを見てみましょう。

_double a[1000];
for (int i = 0; i < 1000; ++i) {
    a[i] = sin(5451939907183506432.0);
}
double d = sin(5451939907183506432.0);
cout << a[500] << endl;
cout << d << endl; 
_

X86 VC++コンパイラで実行すると、出力は次のようになります。

_0.522491
0.528463
_

配列を埋めるときsinは___vdecl_sin2_の呼び出しにコンパイルされ、単一の操作がある場合は___libm_sse2_sin_precise_の呼び出しにコンパイルされるようです(_/fp:precise_)。

私の意見では、あなたの数はsin計算が大きすぎて、異なるコンパイラから同じ動作を期待したり、一般的に正しい動作を期待したりするには大きすぎます。

36
DAle

cppreference によると:

Argの大きさが大きい場合(C++ 11まで)、結果はほとんどまたはまったく意味を持たない可能性があります。

これが問題の原因である可能性があります。この場合、argが大きくならないように手動でモジュロを実行する必要があります。

9
SirGuy