double, 100で割ってから100倍する – プログラミング – Home

double, 100で割ってから10...
 
通知
すべてクリア

double, 100で割ってから100倍する

固定ページ 1 / 2

pen
 pen
(@pen)
ゲスト
結合: 17年前
投稿: 7
Topic starter  

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 に値を格納するのとしないのとでは、何が違うのでしょうか?


引用解決済
トピックタグ
maru
 maru
(@maru)
ゲスト
結合: 17年前
投稿: 358
 

> 疑問: 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)では一致判定を==で行うことは一般ではありません。
数学的に一致するはず、というという思い込みは通じません。


返信引用
wclrp ( 'o')
 wclrp ( 'o')
(@wclrp ( 'o'))
ゲスト
結合: 18年前
投稿: 287
 

推測
コンパイラが最適化しているのかもね。
CPUやFPU等が浮動小数点を計算するのと
double型が完全一致しなければいけない規則じゃないし。


返信引用
wclrp ( 'o')
 wclrp ( 'o')
(@wclrp ( 'o'))
ゲスト
結合: 18年前
投稿: 287
 

こういう場合俺は最適化できないように

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進数に変換した
ものであって真実の確認にならないからね。


返信引用
pen
 pen
(@pen)
ゲスト
結合: 17年前
投稿: 7
Topic starter  

#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

うーむ、なんで結果が異なるのだろう…。


返信引用
maru
 maru
(@maru)
ゲスト
結合: 17年前
投稿: 358
 

実際にやってみた(最初のプログラム)。
結果は
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

何が違うんでしょうね。


返信引用
とおりすがり
 とおりすがり
(@とおりすがり)
ゲスト
結合: 23年前
投稿: 180
 

最適化設定や浮動小数点関連のオプション設定にもよるだろうし
コンパイラのバージョンにも影響受けるかも

某社の音声コーデックのリファレンス実装がオプションどういじっても
VC6とVC8でデコード結果一致しなかった…orz


返信引用
wclrp ( 'o')
 wclrp ( 'o')
(@wclrp ( 'o'))
ゲスト
結合: 18年前
投稿: 287
 

testを先に持ってきたか!

最適化で自動inline展開されちゃうとどうかと思ってね


返信引用
wclrp ( 'o')
 wclrp ( 'o')
(@wclrp ( 'o'))
ゲスト
結合: 18年前
投稿: 287
 

CPUやコンパイラによって違うから一概に言えないけど
VCのdoubleがIEEE 754の8バイト
浮動小数点は10バイトで計算しているらしい(違う場合もあるよ)
だからdoubleに一旦入れると精度落ちるよ。


返信引用
pen
 pen
(@pen)
ゲスト
結合: 17年前
投稿: 7
Topic starter  

wclrp ( 'o')さん:

> testを先に持ってきたか!

> 最適化で自動inline展開されちゃうとどうかと思ってね

すみません、よく分からないのですが、testの定義のあとさきで、
コンパイル結果に違いがあるということですか?
inline展開されるのは、inline宣言されたもののみが対象だと思っていたのですが…。


返信引用
pen
 pen
(@pen)
ゲスト
結合: 17年前
投稿: 7
Topic starter  

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++の掲示板なのにすみません)


返信引用
pen
 pen
(@pen)
ゲスト
結合: 17年前
投稿: 7
Topic starter  

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になるのがよく分かりませんね…。


返信引用
YuO
 YuO
(@YuO)
ゲスト
結合: 22年前
投稿: 320
 

> の場合に 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ビットに丸め
られている) と異なった。

ということではないでしょうか。


返信引用
ITO
 ITO
(@ITO)
ゲスト
結合: 22年前
投稿: 1235
 

>ぼくの環境はg++ 3.4.4 cygwinです。(VC++の掲示板なのにすみません)
gccになるとまた話が変わるよ、

>CPUやコンパイラによって違うから一概に言えないけど
>VCのdoubleがIEEE 754の8バイト
このはなしが変わってきます。

doubleを使う以上、値は変わってしまうと思います。
データはintで保存しておき、計算の都度doubleに戻す。
計算後は再びintにして保存する。

どうしても、intを使うのがいやなら、固定小数点を使うしかないと思います。


返信引用
yoh2
 yoh2
(@yoh2)
ゲスト
結合: 18年前
投稿: 70
 

同じコンパイラでも最適化オプションによって結果が変わることもありますね。
例えば、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) < 小さな数 ) { ... }

とします。

この「小さな数」は、浮動小数点数の精度や、比較するまでに行った
計算の性質を考慮して決定する必要があります。
あまりに小さ過ぎると、一致すべき値が一致せず、大き過ぎると
区別すべき値が同値と判定されてしまうので難しいところ。
このあたりについては、「数値計算」「誤差」あたりで
検索するといろいろと情報が見付かります。


返信引用
固定ページ 1 / 2

返信する

投稿者名

投稿者メールアドレス

タイトル *

プレビュー 0リビジョン 保存しました
共有:
タイトルとURLをコピーしました