C言語関係掲示板

過去ログ

No.73. 実数の誤差


今BCB5を使ってツールを作ってるのですが、どうも理解に苦しむ動作をしてくれます。
普段なんでもかんでも整数でやってしまう実数初心者なので私が間違ってる可能性が高いです。

int ans と double r = 111.1 があります。
やりたいことは簡単で、ans へ r の 10倍 を代入したいだけです。
最初は(1)のようにしたのですが、なぜか1111ではなく1110が入ります。
型変換とか優先順位とかが関連しているのかと思って次に(2)のようにしてみたのですがダメでした。
いろいろ試してみると(3)と(4)は1111が入ってくれました。
これってどういうことなんでしょうか?

・NG
(1) ans = r * 10;
(2) ans = (int)(r * 10.0);
・OK
(3) ans = 111.1 * 10;
(4) r = r * 10;
  ans = r;


>int ans と double r = 111.1 があります。
>やりたいことは簡単で、ans へ r の 10倍 を代入したいだけです。
>最初は(1)のようにしたのですが、なぜか1111ではなく1110が入ります。
>型変換とか優先順位とかが関連しているのかと思って次に(2)のようにしてみたのですがダメでした。
>いろいろ試してみると(3)と(4)は1111が入ってくれました。
>これってどういうことなんでしょうか?

試してみたら私の環境でもほとんどが同じ動きをしたので不思議に思って
いろいろ試してみましたがなかなか思うように動いてくれませんでした。

ということで、もしかしてキャスト自体に問題があるのではなく
ただ単に、浮動小数点の桁落ちが影響しているのではないかと思って
double r = 111.100001
と試したところ思うように動いてくれました。
もしかして、r = 111.1 をキャストしたときに内部的に
111.099999....というような数値になってしまっているのかもしれませんね。
あくまでも憶測の域で仮説をたてているだけなので
鵜呑みにしないでくださいね。

ちなみに
NGと同じ動きをみせた処理系は
Lsic86試食版
BCC5.5
gcc2.9(Win. Linux両方とも)

NGののスクリプトでも正常に動いてくれた処理系は
VC++6.0
の一つだけでした。


ども。

実数型といえども、すべての数を表せるわけではありません。
たとえば0から1の実数はいくつあるか?といえば、答は無限ですね。

計算機の実数は2のべき乗の和で表せるもの(つまり2進数で表現できるもの)
しか正確に表現することができません。111.1(=111+0.1)について考えると
小数点より上(111)は64+32+8+4+2+1ですが、小数点以下(0.1)は2進数では
無限小数になります(計算するのめんどいので略)。無限小数ということは
有限桁で表現できない、つまり計算機上では正確に表現できないということ
になります。で、めんどいので略した部分の、精度が落ちたものを10進で
表すと0.099...とかになると思います。

ちなみに 2^-1=1/2^1=0.5, 2^-2=1/2^2=0.25 .. です。ので0.75とかは
正確に表現できます(^はべき乗)。

>・NG
>(1) ans = r * 10;
>(2) ans = (int)(r * 10.0);

これはおなじです。上記参照。

>・OK
>(4) r = r * 10;
>  ans = r;

これも上記参照。
rを表示してみてください。。とおもいましたが、表示してもたぶんなにも
わかりません。。。

実数の内部表現を調べ、理解した上で、2進数で表示するといいのですが。
紙の上で、0.1の2進表現を計算し、適当な桁で打ち切って、10倍する
というのをやるのもありかと。めんどくさいですが。

2進数とか内部表現とかの話は情報処理技術者試験のページなどを
参考にしてください。

で。

>(3) ans = 111.1 * 10;

これが一番問題です。。

確実なことが言えなくてすみませんが、おそらくコンパイラの最適化時に
すでに(注意深く、あるいは四捨五入して)計算されていて、最初からansに
1111が入ると思われます。興味があれば、最適化オプションで
「定数の畳み込み」(コンパイル時に計算できる式は計算してしまう)という
のが制御できるかマニュアルかなにかで調べてみてください。できるのであれば
そのオプションをON/OFFしてみて結果に影響するか調べてみてください。

これが理由かどうか調べる別の方法として、適当な実数変数に式が違うがおなじ
値を代入するコードを(たとえばr=1とr=0.1*10とか)をそれぞれプログラム
(ファイル)を分けて書いて、コンパイラが生成するコード(もちろん実行結果は
同じ)をバイナリエディタか何かで比較するというのが簡単かな?もちろん
バイナリじゃなくてアセンブラリストをテキストエディタで比較してもOKです。
ただしアセンブラリストを出力するオプションが要りますが。内容は理解する
必要はありません。同じか違うかが確認できればいいので。

では。


ども。

ちょっと訂正。

>(ファイル)を分けて書いて、コンパイラが生成するコード(もちろん実行結果は
>同じ)をバイナリエディタか何かで比較するというのが簡単かな?もちろん

気づいた方もいると思いますが

(もちろん実行結果は同じ)

の部分は、「もちろん」とはいえません。

(実行結果は同じであることが期待される)

くらいが正確でしょうか。。

では。

戻る


「初心者のためのポイント学習C言語」 Last modified:2001.11.15
Copyright(c) 2000-2002 TOMOJI All Rights Reserved