LoginSignup
7
10

More than 5 years have passed since last update.

小数の計算結果を簡単に丸めてはいけない

Last updated at Posted at 2017-02-24

多少なりともプログラミングをしている方であれば、「浮動小数点数には誤差がある」という話は聞いたことがあるかもしれません。

しかし、そのことを普段から意識できているでしょうか?
僕は意識しておらず案の定踏み抜きました。

ありがちな例

ある日、ユーザさんの預金残高に小数点以下切り捨てで 0.5% の利息をつける必要がありました。
かんたんな計算だと思って、僕はこんなコードを書きました。

JavaScript
Math.floor(value * 1.005)

……これ、間違いです。

試しに value1000 を入れてみましょう。1000に 0.5% を足したら1005になるような気がしますが、実際には1004になってしまいます。上記の例は JavaScript ですが、大抵のメジャーな言語では同じ結果になります。手元で試してみましょう。

なぜこんなことになるのでしょうか。1000 * 1.005 をそのまま出力してみると、1004.9999999999999 となります。この値はわずかに1005に達していないので、切り捨てたら1004になってしまうのですね。浮動小数点数は1.005を正確に表現できないため、誤差が生じてしまったようです。

切り捨てと切り上げに気をつける

この誤差は本当に簡単に発生します。次の例は、すべて期待通りの結果にはなりません。

JavaScript
Math.floor(0.6 / 0.2) // 2
0.6 / 0.2             // 2.9999999999999996
JavaScript
// 消費税は小数点以下切り上げ
Math.ceil(900 * 1.08) // 973
900 * 1.08            // 972.0000000000001
JavaScript
let f = 0;
for (let i = 0; i < 10; i++) {
  f += 0.1;
}
Math.floor(f) // 0
f             // 0.9999999999999999

浮動小数点数の計算結果は整数にならないと考えてしまったほうが良いです。

回避策

計算結果を丸める前に、十分下位の桁で四捨五入すれば、この問題は回避できます。

JavaScript
// 小数点第4位で四捨五入
Math.floor((value * 1.005).toFixed(3))
Java
Math.floor(String.format("%.3f", value * 1.005))
PHP
floor(round($value * 1.005, 3))

厳密な計算が求められる場面には向かないかもしれませんが、大抵のアプリケーションではこれで十分でしょう。Math クラスを拡張するなどして、アプリケーション全体から呼べるようにしておくと良いかもしれません。

17/2/25 追記

コメントで指摘を頂きました。Decimal 型を使うことで、変な実装をすることなく誤差のない計算を行うことができます。
Java であれば標準で BigDecimal クラスが使えますし、JavaScript にも decimal.js というライブラリがあります。自分で実装するよりも、こういったものを使うほうが良いでしょう。
(追記ここまで)

まとめ

小数の計算には気をつけましょう。

7
10
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
10