質問
ここです。最初に主張する経過と共に、第二に失敗した:
double sum_1 = 4.0 + 6.3;
assert(sum_1 == 4.0 + 6.3);
double t1 = 4.0, t2 = 6.3;
double sum_2 = t1 + t2;
assert(sum_2 == t1 + t2);
ない場合は、バグ、いかがでしょうか?
解決
これは、あまりにも、私をかましているものです。
はい、浮動小数点数があるため丸め誤差の等しいかどうかを比較すべきではない、とあなたはおそらくそれを知っています。
しかし、この場合には、あなたはそれを再計算し、t1+t2
を計算しています。 の確かにの同じ結果を生成するために持っている?
ここでは、おそらく何が起こっているのです。私はあなたが正しいのx86 CPU上でこれを実行している賭けますか? x86 FPUは、内部レジスタの80ビットを使用するが、メモリ内の値は、64ビットの倍精度として保存されます。
そこでt1+t2
はまず、精度の80ビットで計算された - 精度の64ビットでsum_2
にメモリに記憶されて - - 私は推測し、いくつかの丸めが発生します。アサートするためには、浮動小数点レジスタにバックロードされる、及びt1+t2
精度の80ビットで再び、再び計算されます。だから今は、より高い精度(80ビット)で計算したsum_2
、と以前の64ビット浮動小数点値に丸めたt1+t2
を、比較している - それは値が正確に同一ではない理由です。
の編集のなぜ最初のテストに合格していますか?この場合、コンパイラは、おそらく64ビット量としてコンパイル時間および格納するに4.0+6.3
を評価 - 割り当てのためとアサートの両方。そう同じ値が比較され、アサートが通過する。
の第二編集のここでのアセンブリコードだコメントで、コード(GCCのx86)の第二部のために生成された - かなりの上で概説したシナリオを、次のとおりです。
// t1 = 4.0
fldl LC3
fstpl -16(%ebp)
// t2 = 6.3
fldl LC4
fstpl -24(%ebp)
// sum_2 = t1+t2
fldl -16(%ebp)
faddl -24(%ebp)
fstpl -32(%ebp)
// Compute t1+t2 again
fldl -16(%ebp)
faddl -24(%ebp)
// Load sum_2 from memory and compare
fldl -32(%ebp)
fxch %st(1)
fucompp
興味深いサイドノート:これは最適化せずにコンパイルされました。それは-O3
でコンパイルされます場合は、コンパイラは最適化のすべてののコードを離れたの。
他のヒント
あなたは浮動小数点数を比較しています。それをしない、浮動小数点数は、いくつかの状況に固有の精度誤差を持っています。代わりに、2つの値の差の絶対値を取り、値がいくつかの少数(イプシロン)未満であることを主張する。
void CompareFloats( double d1, double d2, double epsilon )
{
assert( abs( d1 - d2 ) < epsilon );
}
これは、浮動小数点数が実装されている方法で行うには、コンパイラとは何の関係も、すべてを持っていません。ここではIEEEの仕様があります:
http://www.eecs.berkeley.edu/~wkahan/ ieee754status / IEEE754.PDFする
私は自分のIntel Core 2 Duoプロセッサ上の問題を複製してきた、と私はアセンブリコードを見ました。ここで何が起こっているのです:あなたのコンパイラがt1 + t2
を評価するとき、それがない。
load t1 into an 80-bit register
load t2 into an 80-bit register
compute the 80-bit sum
それはsum_2
に保存するとき、それん。
round the 80-bit sum to a 64-bit number and store it
次に==
の比較は、64ビットの合計に80ビットの合計を比較し、それらが異なるなら、主に小数部分ので、0.3は正確にバイナリ浮動小数点数を使って表現することはできませんので、あなたが「比較します二つの異なる長さに切り捨てられた「小数を繰り返す(実際繰り返すバイナリ)。
本当に刺激のは、あなたがgcc -O1
またはgcc -O2
でコンパイラ場合、gccはコンパイル時に間違った算術演算を行うことで、問題が消えます。多分これは標準に従ってOKですが、それはgccが私のお気に入りのコンパイラではないことだけでもう一つの理由です。
P.S。私は==
は、64ビットの合計と80ビットの合計を比較することを言うとき、私は本当に意味のコースは、64ビットの和の拡張版を比較します。あなたが考えるようにうまくやるかもしれません。
sum_2 == t1 + t2
タグに解決
extend(sum_2) == extend(t1) + extend(t2)
と
sum_2 = t1 + t2
タグに解決
sum_2 = round(extend(t1) + extend(t2))
浮動小数点の素晴らしい世界へようこそ!
を比較する場合は浮動小数点数のための近さで通常対して相対的な差として定義される
if (abs(x) != 0 || abs(y) != 0)
rel_diff (x, y) = abs((x - y) / max(abs(x),abs(y))
else
rel_diff(x,y) = max(abs(x),abs(y))
例えば、
rel_diff(1.12345, 1.12367) = 0.000195787019
rel_diff(112345.0, 112367.0) = 0.000195787019
rel_diff(112345E100, 112367E100) = 0.000195787019
その考え方としては、測定の数主桁の番号を通;を利用する場合は、-log10の0.000195787019す3.70821611、さらに複数の拠点に10桁にしています。
あなたが必要かを判断する二つの浮動小数点数を均等にするべきなのようなもの
if (rel_diff(x,y) < error_factor * machine_epsilon()) then
print "equal\n";
が機εは、最小の数値で開催され、仮数の浮動小数点ハードウェアを使っています。ほとんどのコンピュータ言語での呼び出しの値です。error_factorを基盤にする必要がある番号の桁があると考えま消費されることによる丸め誤差及びその他の算数xとy.例えば、私は知っていることをxとyのために約1000summationsいなかった任意の範囲の数総括は、セットerror_factor約100です。
のための追加これらのリンクから自分のポスト:
- en.wikipedia.org/wiki/Relative_difference
- en.wikipedia.org/wiki/Machine_epsilon
- en.wikipedia.org/wiki/Significand (仮数)
- en.wikipedia.org/wiki/Rounding_error
これは、例1で、あなたは80ビットの内部レジスタへの64ビットのダブルを比較終わることかもしれません。 GCCが2例のために発する組み立て説明書を見て啓発かもしれ...
比較は、本質的に不正確です。たとえば、あなたは、多くの場合、0.0 == 0.0
はの偽の帰国見つけることができます。これは、FPU店やトラック番号方法が原因です。
ウィキペディアはに述べています:
平等のためのテストは問題があります。数学的に等しい2つの計算の配列は十分異なる浮動小数点値を生成することができる。
あなたは比較のための許容範囲ではなく、正確な値を与えるために、デルタを使用する必要があります。
この「問題」これらのオプションを使用することによって「固定」することができます:
-msse2 -mfpmath = SSE
このページで説明したように:
http://www.network-theory.co.uk /docs/gccintro/gccintro_70.htmlする
私は、これらのオプションを使用すると、両方渡さアサートます。