- ベストアンサー
丸め誤差への対処法についての疑問
丸め誤差についてですが、 浮動小数点数の内部表現は2進数である為、循環小数となる「0.1」等を含んだ計算結果は誤差が 生じる事が多く、その対策として「整数にしてから計算し、その後割り戻す」 というのをよく目にします。 例えば、javascriptで行う下記のような計算です。 (1)0.1×6=0.6000・・・01 ※誤差が出る (2)0.1×10×6=6 ※整数にしてから計算 (3)(0.1×10×6)/10=0.6 ※割り戻す この時、(2)の「0.1×10」の箇所で誤差が生じないのは何故でしょうか? 「0.1」という数字自体が2進数に変換した時既に誤差が生じているのであれば、 誤差が生じている数字に10を掛けてもやはり誤差が残ってしまう気がするのですが。 また、(3)の割り戻し処理では結果が少数となりますが、この時に誤差が生じる事はないのでしょうか?
- みんなの回答 (5)
- 専門家の回答
質問者が選んだベストアンサー
(2)でも(3)でも誤差はあります。 整数化するのは、誤差の蓄積をなくし、 出来るだけ誤差を少なくするためのものです。 完全に誤差をなくすことは出来ません。 具体例を見た方が分かりやすいでしょう。 -------------------------- var x = 3.3/10; document.write("x="+x+"<br>"); // ・・・ (1) var y = Math.round(x * 100 + 0.5); // ・・・ (2) document.write("y="+y+"<br>"); var ansx = 0; var ansy = 0; for(var i = 0; i < 10000; i++){ // ・・・ (3) ansx += x; ansy += y; } ansx /= 10000; // ・・・ (4) ansy /= 1000000; // ・・・ (5) document.write("ansx="+ansx+"<br>"); // ・・・ (6) document.write("ansy="+ansy+"<br>"); // ・・・ (7) -------------------------- (1):0.32999999999999996 誤差が発生しています。 (Windows XP、IE6.0の場合、ブラウザにより多少異なるかも) (2):yはxを100倍して整数化します。 round関数で明確に整数へ変換します。(0.5を足して、四捨五入しています。) 整数に変換することにより、誤差は切り捨てられます。 (3):それぞれforループで1万回足します。 (4):xの合計を1万で割ります。 1万倍して、1万で割った事になるので、元の数に戻るはずです。 (5):yは100倍してあったので、合計を100万で割ります。 (6):0.3299999999999554 (1)と比べて、誤差が増えているのがわかります。 (7):0.33 誤差はありません。 この例で誤差が出ないだけで、 (7)でも誤差が生じる可能性はありますが、 (6)と比べると少ないものになります。 ------------------------------------- 誤差そのものは小さいものなので、 数回の計算なら整数化する必要はないでしょう。 しかし、何回も繰り返し計算をする場合、 誤差が蓄積し、大きくなる可能性があります。 そういう場合に、整数化することにより、 誤差を少なくすることが出来ます。
その他の回答 (4)
- venzou
- ベストアンサー率71% (311/435)
#3です。 >ところで、Math.round(x * 100 + 0.5); の箇所ですが、0.5 を足してからroundで >四捨五入しているのは何故でしょうか? すいません、間違いました。 floor関数とごっちゃになっていました。 round関数の場合0.5を足す必要はありませんね。失礼しました。 Math.round(x * 100); 余談: 四捨五入用の関数が用意されていない場合、 0.5を足して切り捨てるという方法がよく使われます。 (ただし、負の数の場合に問題がある)
お礼
お返事ありがとうございます。 勘違いだったんですね。 余談の四捨五入の話も参考になりました。
- 神崎 渉瑠(@taloo)
- ベストアンサー率44% (1016/2280)
「割るだけ」の場合ではそのまま割ってから誤差を切り捨てや四捨五入で削除します。 割り算とかけ算の両方を行う場合は かけ算を行ってから割り算を行い、誤差を削除します。 > (2)0.1×10×6=6 ※整数にしてから計算 誤差処理をしてないので、本当は 6.0000000...1 です。 最近のブラウザはうまく処理してるので6と表示されます。*これが「6」と表示される要因。 ”安物の”電卓で 5/3*6 と 5*6/3 を比べてみてください。
お礼
整数化して計算したから誤差は安心というわけではなく、本当は誤差が出てるんですね。 アドバイス頂いたように、切り捨てや四捨五入で対応するようにします。 ありがとうございました。 ちなみに、私の電卓では 5/3*6と5*6/3は違う結果になりました。安物?(笑)
(1)0.1×6=0.6000・・・01 ※誤差が出る (2)0.1×10×6=6 ※整数にしてから計算 (3)(0.1×10×6)/10=0.6 ※割り戻す ではなくて (1)0.1×6=0.6000 (2)(1×6)=6 (3)(1×10×6)/100=0.6 という話じゃないのかな。 単なる服飾デザイナですが、整数にしてから計算とか割り戻す例とすれば・・・。
お礼
例の書き方があまり良くなかったでしょうか。 アドバイスありがとうございます。
- SAYKA
- ベストアンサー率34% (944/2776)
演算結果は環境によるけれど予測している通り(2)でも0.1そのものが誤差を持つ場合が否定できないよね。 内部的には高速化するために循環の小数の2進数かもしれなけれど実は 1x(10^(-1)) という値の保持の仕方をしていたらちょっと話が変わる。 昔は兎に角小さく、小容量にって事で小数でも完全に2進数化していただろうけれど演算精度を上げるために違う保持をしているかもしれない。 http://journal.mycom.co.jp/column/architecture/092/index.html ・・・でもやっぱり2進数みたい・・・答えにならなかったけど参考に。
お礼
URL参考にさせて頂きました。 ありがとうございます。
お礼
具体例までご提示頂き、ありがとうございます。お手間だったのではないでしょうか? 「整数にしてから計算し、その後割り戻す」というのは、誤差をなくす為の方法ではなく 誤差を少なくする為の方法なんですね。 (2)のように、整数化した場合に誤差が出ない時は、具体例(7)のようにたまたま出ていないだけか、 誤差が小さすぎて無視されている(?)と考えるようにします。 ところで、Math.round(x * 100 + 0.5); の箇所ですが、0.5 を足してからroundで 四捨五入しているのは何故でしょうか?