Visual C++ math.h のバグ
-
20-09-2019 - |
質問
プロジェクトをデバッグしていましたが、バグが見つかりませんでした。ついに見つけました。コードを見てください。すべてOKだと思っていても、結果は「OK!」となるでしょう。わかりました!わかりました!」、そうでしょう?次に、VC でコンパイルします (vs2005 と vs2008 を試しました)。
#include <math.h>
#include <stdio.h>
int main () {
for ( double x = 90100.0; x<90120.0; x+=1 )
{
if ( cos(x) == cos(x) )
printf ("x==%f OK!\n", x);
else
printf ("x==%f FAIL!\n", x);
}
getchar();
return 0;
}
魔法の倍精度定数は 90112.0 です。x < 90112.0 の場合はすべて問題ありませんが、x > 90112.0 の場合 -- いいえ!cos を sin に変更できます。
何か案は?sin と cos は周期的であることを忘れないでください。
解決
これを次のようになります。のhttp://www.parashift .COM / C ++ - FAQ-LITE / newbie.html#FAQ-29.18 の
私はそれを受け入れるのは難しいけど、ほとんどの人が期待するように浮動小数点演算は、単純に動作しません。さらに悪いことには、違いのいくつかは、あなたの特定のコンピュータの浮動小数点ハードウェアおよび/またはあなたが特定のコンパイラで使用の最適化設定の詳細に依存しています。あなたはそれを好きではないかもしれませんが、それはそれは方法です。余談物事がの方法についてのあなたの仮定を設定することです「それが得る」ための唯一の方法、彼らは実際にのようなものを振る舞うと受け入れるようにのはずの振る舞い...
やります(単語「しばしば」に重点を置いて、行動等、コンパイラ、ハードウェアに依存します):浮動小数点演算と比較は、多くの場合、多くの場合、特殊レジスタが含まれている特殊なハードウェアで実行され、これらのレジスタは、多くの場合、より多くのビットを持っています
double
より。すなわち、中間浮動小数点演算がしばしばsizeof(double)
より多くのビットを有することを意味し、浮動小数点値をRAMに書き込まれると、それはしばしば、多くの場合、精度のいくつかのビットを失う、切り捨てられます...ちょうどこのことを覚えている:浮動小数点の比較は難しいと微妙と危険に満ちています。注意してください。浮動小数点の実際の動作方法は、ほとんどのプログラマは、それがの仕事にのはずだと思う傾向にある道は異なっています。あなたは浮動小数点を使用する場合、あなたはそれが実際にどのように動作するかを学ぶ必要があります...
他のヒント
他の人が指摘したように、VS 数学ライブラリは x87 FPU で計算を実行し、型が double であっても 80 ビットの結果を生成します。
したがって:
- cos( ) が呼び出され、x87 スタックの最上位にある cos(x) を 80 ビット浮動小数点として返します。
- cos(x) は x87 スタックからポップされ、double としてメモリに格納されます。これにより、64 ビット浮動小数点数に丸められます。 値を変更します
- cos( ) が呼び出され、x87 スタックの最上位にある cos(x) を 80 ビット浮動小数点として返します。
- 丸められた値がメモリから x87 スタックにロードされます
- cos(x) の丸められた値と丸められていない値は等しくありません。
多くの数学ライブラリとコンパイラは、利用可能な場合は SSE レジスタの 64 ビット浮動小数点で計算を実行するか、比較前に値を強制的に格納して丸めるか、実際の計算で最終結果を格納して再ロードすることによって、この問題からユーザーを保護します。 cos( )の。あなたが使用しているコンパイラとライブラリの組み合わせは、それほど寛容なものではありません。
COS(X)== COS(X)手順リリースモードで生成された
00DB101A call _CIcos (0DB1870h) 00DB101F fld st(0) 00DB1021 fucompp
の値は、それ自体と比較し、次に、一度計算し、次いでクローニングし - 結果がOKであろう
デバッグモードで同じます:
00A51405 sub esp,8 00A51408 fld qword ptr [x] 00A5140B fstp qword ptr [esp] 00A5140E call @ILT+270(_cos) (0A51113h) 00A51413 fld qword ptr [x] 00A51416 fstp qword ptr [esp] 00A51419 fstp qword ptr [ebp-0D8h] 00A5141F call @ILT+270(_cos) (0A51113h) 00A51424 add esp,8 00A51427 fld qword ptr [ebp-0D8h] 00A5142D fucompp
さて、奇妙なことが起こる。
1. Xはfstackにロードされている(X、0)
2. Xは、通常のスタック(トランケーション)に格納され
3.コサインが計算され、フロートスタック
になります
4. Xが再びロードされ
(今のところ、私たちは「左右対称」していると、切り捨て)5. Xは通常のスタック上に保存されている
6.スタック上にあった第1余弦の結果をメモリに格納され、今、別の切り捨ては、第1値を発生
一度だけ
7コサインがあれば浮動スタック上に、第二の結果を計算されますが、この値は切り捨てられました
8.第一値はfstackにロードされますが、この値は
(一回一回の後、余弦を計算する前に)二度切り捨てられました
9.これらの2つの値を比較して - 私たちは、丸め誤差を取得している。
あなたは<ストライキ>決してストライキ>ほとんどの場合、平等のためのダブルスを比較するべきではありません。あなたは、あなたが期待するものを取得できない場合があります。
浮動小数点レジスタは、(現在のインテル機で、FPUレジスタは80ビット、64ビットのダブル対である)メモリの値とは異なるサイズを有することができます。コンパイラは最初の余弦を計算するコードを生成している場合、メモリに値を格納し、値が(これは80〜64ビットから問題を丸めに)異なる可能性が第二余弦を計算し、レジスタのことから、メモリ内の値を比較します。
浮動小数点値は、ビットトリッキーです。浮動小数点comparissons Googleのます。
コンパイラは、64ビットのダブル値を比較終わるコードを生成している可能性があります
80ビットの内部浮動小数点レジスタ。平等のための浮動小数点値をテスト
エラーこれらの種類の傾向がある - あなたは、ほとんどの場合、のような「ファジー」比較やって方がいいでしょう(ファブ(val1とは - とval2)
float値をインクリメントし、テストします。あなたがする必要がある場合は、ちょうど上のループのために別のint型のLCVを作成します。
この場合、それは簡単です:
for ( int i = 90100; i<90120; i+=1 ) {
if ( cos(i) == cos(i) )
printf ("i==%d OK!\n", i);
else
printf ("i==%d FAIL!\n", i);
}
どのように問題を回避しますか?
if ( (float)cos(x) == (float)cos(x) )