たけぼーの備忘録

暇を持て余した人間の無意味なブログ

BigDecimalで誤差が出てしまった話

こんにちは、たけぼーです。
今日はJavaで正確な数値計算を行うための、BigDecimalについてです。

Androidのプログラミングをしていたら、誤差のない計算が必要になる時がありますよね。
例えば、電卓なんかを作るなら誤差が出たら大変です。

誤差が出る原因は、値をfloat,double型として計算するからです。
どうも、10進数を2進数に直して計算すると、正確に2進数に変換できないということが原因だそうです。

こんなときは、BigDecimal型という特殊な型を使って計算を行えばよいのです。
BigDecimalは、誤差を生じさせないように計算することを目的としたものだそうです。

私もBigDecimalで計算すればいいや、と思って使ったのですが・・・

まだ誤差が出る!!

おいおい、なんなんだこれは。誤差が出ないって言ったから使ったのに・・・

16×0.03(=0.48が正解)を小数点以下第2位まで切り捨て計算したところ、堂々と0.47と表示しやがる。
なんなんだ君は、という気分ですが。

どう調べても、BigDecimalでは絶対に誤差は出ません!と書いてあるので、どこが間違いなのかわかりませんでしたが、やっと解決策を見つけました。

では、解決策へ参りましょう。

BigDecimalで誤差が出ないようにする

原因は、どのようにBigDecimalの生成のときにdouble型が混ざっていたからです。
元のソースは以下のとおり。

//これでは結果が0.47となる
double a = 0.03;
double b = 16;
・・・
BigDecimal db_a = new BigDecimal(a);
BigDecimal db_b = new BigDecimal(b);
BigDecimal db_c = a.multiply(b).setScale(2,RoundingMode.FLOOR);
double ans = db_c.doubleValue();

ここでの失敗の原因は

BigDecimal db_a = new BigDecimal(a);

ここです、悪の根源は。
この生成部分でカッコ内にdouble型のデータを渡していることです。
double型自体が元から誤差を含んでいるものなので、このままでは誤差が受け継がれてしまうようです。

誤差が出ないためには以下のように変更すれば良いのです。

//これで結果は0.48となる
//double から Stringに変更
String a = 0.03;
String b = 16;
・・・
//String型で渡される
BigDecimal db_a = new BigDecimal(a);
BigDecimal db_b = new BigDecimal(b);
BigDecimal db_c = a.multiply(b).setScale(2,RoundingMode.FLOOR);
double ans = db_c.doubleValue();

a,bをString型に変更することで、BigDecimalの生成時には文字列として渡されるので、誤差は出なくなります。
実際に計算すると、ちゃんと正確な値が出ました。

ということで、今日はここまで。
ではまた。

参考

今回参考にさせていただいたのはこちら。非常に助かりました。

www.slideshare.net