浮動小数の等値比較は要注意

浮動小数は近似値である。
大抵の人がそれを理解しているはずですが、以下のコードでcos(a) != cos(b)が成立する可能性がある理由を挙げてください。私が確認した限り、VC++ 6, 2003の/Od(最適化無効)で発生、2005は発生しませんでした。

#include <iostream>
#include <cmath>

int main() {
	volatile double a = 1.0;
	volatile double b = 1.0;

	if(cos(a) != cos(b)) {
		std::cout << &quot;cos(a) != cos(b)&quot; << &quot;n&quot;;
	}
	return 0;
}


CやC++において、上記の結果は不定です。(※1)
関数の戻り値が、精度の高いCPUのレジスタに置かれたまま比較されたか、8バイト程度に一旦落とされた後に比較されたかで結果が変わります。つまり、片方が高精度なレジスタで、片方が8バイトに丸められていた場合に不等式が成立してしまったわけです。
正しく比較する方法はMSDNの浮動小数点数の精度の低下あたりを読むと良いでしょう。
ネタとしては以上ですが、ここまでの話が良く分からなかった人は以下の追試。
性質は異なるものの、言いたいことは同じです。

#include <iostream>
#include <cmath>

int main() {
	int count = 0;

	for(double f = 0.0; f != 1.0; f += 0.1) {
		count++;
	if(count >= 1000) break;
	}

	std::cout << "count:"<< count << "n";
	return 0;
}

大抵のコンパイラでforループが10回で終わらない理由を考えてください。

どうしても分からない方はcount++の行にブレークポイントを張るとよいでしょう。

※1 C言語の最新仕様であるC99では、5.1.2.3章において、一度レジスタから記憶域に書き出して、読み込めと言及されていました。つまり、GCCでC99としてコンパイルする場合に、上記の現象は発生しないと思われます。(GCCのC99対応はまだ完全では無いと聞いていますが)

—-

[MSDN] 浮動小数点数の精度の低下

(06/10/26) おそらく不定→不定に変更。

Leave a Reply

4 Comments on "浮動小数の等値比較は要注意"

Notify of
avatar
Sort by:   newest | oldest | most voted
小人物
Guest

後半は一致しないのが分かりますが、
前半はかなりショッキングです。
こうなると浮動小数点数の等値比較そのものにどれほどの意味があるのでしょう?

konuma
Guest

コメントありがとうございます。
等値比較をするなって事で、分かりにくそうな例を出してみました。単純な計算でも倍精度で表現しきれないものなら同じ事が起こり得ます。
ではVC++2005で何故うまくいってしまうかというと、コンパイラの動作が変わったからです。
■ /fp (浮動小数点の動作の指定)
http://msdn2.microsoft.com/ja-jp/library/e7s85ffb.aspx
逆に精度の高い計算がいらないのならば、コンパイルスイッチだけで、高速化できます。

黒兵衛
Guest

私も二つ目のはわかるのですが、一つ目のが良くわかりませんでした。
基本的な疑問なのですが、doubleは8バイトだから8バイトに丸められるのが普通で、
「精度の高いCPUのレジスタに置かれたまま比較」する仕様の方が規格外で
2003コンパイラのバグなのではと思うのですがどうでしょうか?
(私はC暦は長いのですが、それほど詳しくはないです。)
でも、このような単純なケースでもうまくいかない事があるというのは参考になりました。
浮動少数の計算はCPUによって実装がまちまちで
ネットゲームなどで調停をとるのが大変だという話を聞いたことがあります。
以下余談

ko-numa
Guest

ちょっと裏を取ってみました。
>2003コンパイラのバグなのではと思うのですがどうでしょうか?
残念ながら、C95や標準C++の仕様の範囲です。しかし、C99を確認したところ、5.1.2.3章の例4で言及されており、CPUは一度記憶域に書き出し読み出す必要がある、となっていました。
ちなみに、Visual C++は2005を含め、C95, 標準C++準拠です。
元々浮動小数点を使う人は処理速度を優先してるわけだし、これがきっちり既定されると、プログラムによっては相当遅くなってしまうでしょう。C99の対応が改良と言い切れない部分も有り、今まで通りコンパイルスイッチとかで対応したほうが、私は良いと思います。

wpDiscuz