はじめに
Javaを用いて円の面積を求める計算を行ったところ、丸め誤差が発生しました。
BigDecimalクラスを利用することで丸め誤差を回避することができたので、その経験を記事にします。
認識の甘い部分が多々あるかと思います、お気づきの際はご指摘いただけたら幸いでございます!
まずは結論から
Double型ではなくBigDecimalクラスを利用することで、丸め誤差を回避できました。
実行環境
Java 15.0.1
意図しない挙動が発生したソースコード(Double型の利用)
円の面積S
は円周率をπ
, 半径をr
としたとき、S =π * r * r
で求めることができます。
ここでは円周率を3.14
, 半径を6
として計算を行いました。
113.04
の結果を想定していたのですが、113.03999999999999
という結果が得られました。
public class Main {
public static void main(String[] args) {
final double pi = 3.14;
final double radius = 6;
final double area = pi * radius * radius;
System.out.println(area);
}
}
// 113.03999999999999
意図しない挙動の原因を探る
ほとんどの10進数の「小数」は、2進数に正しく変換できないから
小数計算の誤差 0.1 + 0.2 が 0.30000000000000004 になる理由
10進数を計算する場合であっても、コンピュータ上では以下のようなフローで計算が行われます。
- 10進数を2進数に変換する
- 2進数で計算を行う
- 計算結果を10進数に戻す
小数を2進数で表そうとすると、ほとんどの場合において無限に続く数字で表すことになってしまい、
最終的にdouble型(64ビット型浮動小数点数表現)では表現しきれなくなるために、丸め誤差が発生します。
丸め誤差とは、ある桁以降を無視することによって生じる誤差のことです。
丸め誤差、打ち切り誤差、情報落ち、桁落ちの意味と例
10進数の小数を2進数に変換した場合、どのような数値になるかはこちらで簡単に測定できます。
ちなみにこちらを利用して3.14
を2進数に変換すると、0000 0011 . 0010 0011 1101 0111 0000 1010 0011 1101 0111 0000 1010 0011 1101 0111(64bitを超えたため、小数点以下の一部を削除しました)
と出力されました。
丸め誤差を回避できるBigDecimalクラスとは
意図しない丸めを許さない
double/floatのプリミティブの計算では丸めが指定できなかったが、BigDecimalでは計算が全てメソッドなので引数として丸め方法を渡すことができる。
~中略~
整数と10のマイナス乗で表す
浮動小数点方式により丸めてしまう問題については、整数と10のマイナス乗による表現で回避されている。BigDecimalは内部的には次のように値を保持している。
整数であれば小数と違い2進数で循環することはないので、それによる丸めは心配ない。10のマイナス乗で表現することで値としても小数と同値を表現可能になっている。これによって、メモリに乗せただけで丸められてしまうこともなくなる。
桁数についても200億桁ぐらいまでいけるようなので、ほんとうに特殊な分野以外の人は考慮する必要も無い。
なぜBigDecimalを使わなければならないのか
上記引用から、BigDecimalクラスを用いると、Double型よりも高精度で計算可能であると解釈しました。
(少なくとも10進数小数を2進数変換することによる丸め誤差は発生しない)
また、必要に応じて丸めることもできるようです。
BigDecimalクラスを利用して、意図する挙動を実現した
実際にBigDecimalクラスを利用してみます。
BigDecimalクラスを導入した次のソースコードで、3.14 * 6 * 6 = 113.04
の計算処理に成功しました!
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
final BigDecimal pi = new BigDecimal("3.14");
final BigDecimal radius = new BigDecimal("6");
BigDecimal area = pi.multiply(radius);
area = area.multiply(radius);
System.out.println(area);
}
}
// 113.04
おわりに
BigDecimalクラスと題したものの、BigDecimalクラス / BigDecimal型どちらの表現がより適切なのか、いまいちよくわからずにいます。
(BigDecimalクラスでも、とりあえず間違ってはいないと思うのですが)
有識者の方はコメント頂けたら幸いです。