100で割ってから100倍するプログラムを以下のように書きました。
#include <iostream>
using namespace std;
int main() {
double b = 2 / 100.0;
double c = 2;
double d = b * 100.0;
cout << (c == d) << endl;
cout << (b * 100.0 == c) << endl;
cout << b << endl;
cout << b * 100.0 << endl;
cout << c << endl;
cout << d << endl;
}
実行結果は次のようになります。
1
0
0.02
2
2
2
疑問: c == dがtrueなのに、b * 100.0 == cがfalseになる理由がわかりません。
いったん、double型の変数 d に値を格納するのとしないのとでは、何が違うのでしょうか?
> 疑問: c == dがtrueなのに、b * 100.0 == cがfalseになる理由がわかりません。
桁落ち、丸め誤差が発生しているからでしょう。
double b = 2 / 100.0;
は正確に0.02ではなく近似値になっています。また
double d = b * 100.0;
は正確にはb * 100.0ではなく、丸められて2になっていると考えられます。
# 正確には検証していない。
浮動少数点数(float, double)では一致判定を==で行うことは一般ではありません。
数学的に一致するはず、というという思い込みは通じません。
推測
コンパイラが最適化しているのかもね。
CPUやFPU等が浮動小数点を計算するのと
double型が完全一致しなければいけない規則じゃないし。
こういう場合俺は最適化できないように
double test();
int main() {
double b = 2 / 100.0;
double c = 2;
double d = b * 100.0;
double e = test(b);
cout << (c == d) << endl;
cout << (c == e) << endl;
cout << (b * 100.0 == c) << endl;
cout << b << endl;
cout << b * 100.0 << endl;
cout << c << endl;
cout << d << endl;
}
double test(double b)
{
return b * 100.0;
}
これでも(c == e)が真なら・・・・なんだろ?
dとeのメモリの内容を見比べるとか。
cout << b;とかじゃ適当に四捨五入および10進数に変換した
ものであって真実の確認にならないからね。
#include <iostream>
using namespace std;
double test(double b)
{
return b * 100.0;
}
int main() {
double b = 2 / 100.0;
double c = 2;
double e = test(b);
cout << (c == e) << endl;
cout << (c == test(b)) << endl;
}
上のコードを実行すると以下のようになりました:
1
0
うーむ、なんで結果が異なるのだろう…。
実際にやってみた(最初のプログラム)。
結果は
1
1
0.02
2
2
2
環境はWindows XP Pro SP2 , Microsoft Visual Studio 2005
Version 8.0.50727.42 (RTM.050727-4200)
Microsoft Visual C++ 2005 77971-009-0000007-41987
Debug, Releaseともに同じ結果でした。
ついでに後のプログラム
1
1
何が違うんでしょうね。
最適化設定や浮動小数点関連のオプション設定にもよるだろうし
コンパイラのバージョンにも影響受けるかも
某社の音声コーデックのリファレンス実装がオプションどういじっても
VC6とVC8でデコード結果一致しなかった…orz
testを先に持ってきたか!
最適化で自動inline展開されちゃうとどうかと思ってね
CPUやコンパイラによって違うから一概に言えないけど
VCのdoubleがIEEE 754の8バイト
浮動小数点は10バイトで計算しているらしい(違う場合もあるよ)
だからdoubleに一旦入れると精度落ちるよ。
wclrp ( 'o')さん:
> testを先に持ってきたか!
> 最適化で自動inline展開されちゃうとどうかと思ってね
すみません、よく分からないのですが、testの定義のあとさきで、
コンパイル結果に違いがあるということですか?
inline展開されるのは、inline宣言されたもののみが対象だと思っていたのですが…。
maruさん:
> 環境はWindows XP Pro SP2 , Microsoft Visual Studio 2005
> Version 8.0.50727.42 (RTM.050727-4200)
> Microsoft Visual C++ 2005 77971-009-0000007-41987
> Debug, Releaseともに同じ結果でした。
ぼくの環境はg++ 3.4.4 cygwinです。(VC++の掲示板なのにすみません)
wclrp ( 'o')さん:
>CPUやコンパイラによって違うから一概に言えないけど
>VCのdoubleがIEEE 754の8バイト
>浮動小数点は10バイトで計算しているらしい(違う場合もあるよ)
>だからdoubleに一旦入れると精度落ちるよ。
だとしても、
double test(double b)
{
return b * 100.0;
}
int main() {
double b = 2 / 100.0;
double c = 2;
double e = test(b);
cout << (c == e) << endl;
cout << (c == test(b)) << endl;
}
の場合に c == eがtrueで、c == test(b)がfalseになるのがよく分かりませんね…。
> の場合に c == eがtrueで、c == test(b)がfalseになるのがよく分かりませんね…。
コンパイル後のアセンブリコードを見ていないので推測ですが,
c == eではFPUから一旦主記憶上のeにデータが移動して,再度cとeの値をFPUに移動して
比較した。
eに代入する時に80ビット→64ビットの変換によって丸めが発生し,結果としてcとeの値
が一致した
c == test(b)では,test(b)の結果はFPUに残したままcの値をFPUに移動して比較した。
この時,test(b)の結果は64ビットに丸められたことがないので,cの値 (64ビットに丸め
られている) と異なった。
ということではないでしょうか。
>ぼくの環境はg++ 3.4.4 cygwinです。(VC++の掲示板なのにすみません)
gccになるとまた話が変わるよ、
>CPUやコンパイラによって違うから一概に言えないけど
>VCのdoubleがIEEE 754の8バイト
このはなしが変わってきます。
doubleを使う以上、値は変わってしまうと思います。
データはintで保存しておき、計算の都度doubleに戻す。
計算後は再びintにして保存する。
どうしても、intを使うのがいやなら、固定小数点を使うしかないと思います。
同じコンパイラでも最適化オプションによって結果が変わることもありますね。
例えば、penさんの2つめのプログラムをgcc-4.1.2で (これまたVC++
から離れてしまってすみません)、最適化オプションを-O0(最適化なし)から、
-O3(強最適化)まで試してみたところ、以下のような結果になりました。
-O0: (c == e) → 1, (c == test(b)) → 0
-O1: (c == e) → 0, (c == test(b)) → 0
-O2: (c == e) → 0, (c == test(b)) → 0
-O3: (c == e) → 1, (c == test(b)) → 1
アセンブリ出力を見たところ、以下のような傾向になっていました。
-O0については、ソースの字面通りに実行しているようで、
結果が異なるのは多分YuOさんの考察通り。
-O1と-O2については、test(b)を一度だけ呼びだして
その結果を再利用。ただし丸められてcと一致せず。
-O3については、そもそもtest(b)の呼出はせず、
そもそも浮動小数点関連の計算すら一切行わずに直接1を
出力しています。
コンパイル時にすべての計算を終えてしまい、その際、
2 と 2 / 100.0 * 100.0 は同一だと判定したのでしょう。
整数演算では、最適化オプション次第で結果が変わってしまう
ことは考えられない(もしそのようなことがあったら
未定義、未規定の罠に引っ掛かったかコンパイラのバグ)
ですが、浮動小数点数演算については、このようなゆらぎが
規格上認められています。
なぜ結果が一定しないかを考えるのは楽しいですが、(実際
盛り上がりましたしね) 実用的には同値性を比較したい数同士が
厳密に一致することはまずあり得ないと思っておくのが無難かと。
以下はもうご存知かもしれませんが、一応書いておきます。
厳動小数点数の同値判定は、 == で直接比較せずに、2つの数の差の
絶対値を見ることが多いです。
例えば、
if( x == y ) { ... }
と書く代わりに
if( fabs(x - y) < 小さな数 ) { ... }
とします。
この「小さな数」は、浮動小数点数の精度や、比較するまでに行った
計算の性質を考慮して決定する必要があります。
あまりに小さ過ぎると、一致すべき値が一致せず、大き過ぎると
区別すべき値が同値と判定されてしまうので難しいところ。
このあたりについては、「数値計算」「誤差」あたりで
検索するといろいろと情報が見付かります。