- おっしゃる通り、表現の誤差の問題です。
とりあえず、簡単に次のようなプログラムを実行してみました。
c
1#include <stdio.h>
2
3int main(void) {
4 printf("%.60f\n", 123.45);
5 printf("%.1f\n", 123.45);
6 printf("%.60f\n", 123.455);
7 printf("%.2f\n", 123.455);
8 return 0;
9}
これを Wandbox により実行した結果が次の通りです (リンクをクリックすると Wandbox へ飛びます) 。
123.450000000000002842170943040400743484497070312500000000000000
123.5
123.454999999999998294697434175759553909301757812500000000000000
123.45
あまり正確ではないですが、とりあえず、内部表現はこんな感じになっているというイメージをつかんでもらえれば。
これらをそれぞれ四捨五入すると質問のような状態になるわけです。
- なかなか難しい問題です。このあたりが C 言語の面倒なところといいますか...
まず、 C 言語においては浮動小数点の表現方法が規格により規定されていません。ただ、どのような表現であってもコンピュータの扱える数字が有限である以上、厳密に表せない数というものがあります。そういうときは近い数字で近似的に扱うということがされます。
さて、ほとんどのコンピュータでは主に二進数で数字を扱いますが、十進数でキリよく表される数も、二進数ではキリよく表されないということがあります (我々の知る循環小数のような) 。ちなみに二進数で表せる数は 2 の累乗の和で表せる数です。 123.4 も 123.45 もそうではありませんので、近似的に表されることになります。それが先の実行結果で後半に現れているゴミです。
printf()
の書式指定 %f
については、指定精度を越える部分については丸められることになっています。
あまり規格に詳しくはないのですが、この丸めがどう行われるかは丸めモードに依存します。丸めモードとは浮動小数の丸め方を指定するものです。最も近い数に丸める、ゼロ方向に丸める、など色々なモードがあります。 (fesetround()
関数で挙動を切り替えることができます。) デフォルトに関する規定は知りませんが、とりあえず我々が使っている環境では何もしなければ最も近い数に丸められます。
これで結果が説明できます。
-
123.45 を小数点以下 1 桁に丸めるとき
候補としては 123.4 と 123.5 が考えられます。どちらに近いか。小数第 2 位以下を見ると 500...02842... となっていますので、この誤差の分だけ 123.5 に近いです。なのでこちらに丸められます。
-
123.455 を小数点以下 2 桁に丸めるとき
候補としては 123.45 と 123.46 が考えられます。どちらに近いか。小数第 3 位以下を見ると 499... となっていますので、誤差があるせいで 123.45 に近いです。よってこちらに丸められます。
ちなみにちょうど 0.5 にあたるときは (0.5 は 1/2 なので二進数できっかり表せます) どちらまでの距離も等しいので、どちらに丸めてもよいことになります。ちなみに手元で試すと切り捨てでしたが、 Windows では違うと聞いたり聞かなかったり (※試していません) 。
- このあたりに関しては経験不足で私では何とも言えないのですが...プログラミングによって何をしたいかによると思います。
例えば数値積分をするとか、計算に重点をおく使い方で、精度ができるだけたくさんいるような場面では、それらの知識はとても大切になってくると思います。情報落ちや桁落ちを防ぐために計算順序を工夫するといったことが常に付きまとってきます。
例えばゲームを作りたいという場面であれば、極端な例でない限りそれほど厳密な精度が要るとは思えません (※何も知らずにしゃべっています) 。少なくとも陰の位置がほんの誤差分だけずれていたり、光の反射が誤差分だけ角度がずれていたとしても気づかないでしょう。
余談ですが、多少の精度を犠牲にして逆平方根を高速に求める手法というのが昔とあるゲームに実装されていたそうです。光の反射を計算するために利用されていたらしいです。このように精度よりむしろ速度などが優先される場合もあると思います。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/07/16 18:21