小数を扱うコードを作成していて、色々と嵌ったのでそのときのメモ。
起きたこと
小数の値を除算する必要があったので、以下のようにBigDecimalを利用して除算するコードを書いた。
public class Divide {
public static void main(String[] args) {
BigDecimal num1 = new BigDecimal(1.1);
BigDecimal num2 = new BigDecimal(100);
System.out.println(num1.divide(num2));
}
}
1.1÷100なので、結果は0.011になるはずだが、実行してみると「0.01100000000000000088817841970012523233890533447265625」と出力される。
なんでやねん、と思ったが使い方が悪かった。
何が起きていたのか
BigDecimalを生成するときに1.1を引数にしているが、こいつがすでに誤差を含んでいるらしい。
10進数小数を2進数小数で表すため、そもそも表現できない値がある。考えてみれば当たり前だ。。。
1.1はその表現できない値であるため、それをもとにBigDecimalを生成すると、その誤差まで保持してしまう。
どうすればいいのか
以下のように、doubleやfloatの値を利用しないようにする。
public class Divide {
public static void main(String[] args) {
BigDecimal num1 = new BigDecimal("1.1");
BigDecimal num2 = new BigDecimal("100");
System.out.println(num1.divide(num2));
}
}
こうすれば、正確な計算が行われる。
それなら、BigDecimalにdoubleを引数とするコンストラクタは必要なのか・・・?
さらに嵌ったこと
以下のように割り切れない値を計算するとArithmeticExceptionが発生する。
public class Divide {
public static void main(String[] args) {
BigDecimal num1 = new BigDecimal("1.1");
BigDecimal num2 = new BigDecimal("3");
System.out.println(num1.divide(num2));
}
}
BigDecimalのdivideメソッドはデフォルトだと割り切れない場合、例外を発生させるらしい。
そのため、以下のように桁数と丸め方を指定して利用する。
public class Divide {
public static void main(String[] args) {
BigDecimal num1 = new BigDecimal("1.1");
BigDecimal num2 = new BigDecimal("3");
System.out.println(num1.divide(num2,2,RoundingMode.FLOOR));
}
}