【Java】doubleを使用した計算による誤差

既存アプリのバグに遭遇した際のメモ。

【問題】
次のような変数があった場合、それらの積はいくつになるだろうか。

double d1=558.3
int i1=100

期待値は言うまでもなく、55830.0でしょう。
しかし、55829.99999999999になっているケースがあったという話。

【原因】
結論から述べると下記2つ。
2つ目で多少ハマった。

1.doubleではなくBigDecimalを使用する(基本中の基本)
2.valが少数の場合、new BigDecimal(val)とすると数値誤差が発生しうる

【検証】
検証コードは次の通り。

package numtest;

import java.math.BigDecimal;

public class NumericalTest {
	public static void main(String[] args){
		double d1=558.3;
		int i1=100;

		System.out.println("d1="+d1);
		System.out.println("i1="+i1);

		System.out.println("単純にdoubleの積だと数値誤差が発生する場合がある");
		System.out.println("【バグ】d1*i1="+d1*i1);

		/**
		 *
		 */
		System.out.println("BigDecimalを使用してみる");
		BigDecimal d1bd = new BigDecimal(d1);
		BigDecimal i1bd = new BigDecimal(i1);

		BigDecimal r1bd = d1bd.multiply(i1bd);
		System.out.println("【バグ】r1bd="+r1bd.doubleValue());

		/**
		 * ①BigDecimalコンストラクタの引数にString.valueOf()を使用
		 * この表現は 1 つの引数を持つ Double.toString メソッドによって返されるものとまったく同じ。
		 */
		d1bd = new BigDecimal(String.valueOf(d1));
		r1bd = d1bd.multiply(i1bd);
		System.out.println("【修正案1】r1bd="+r1bd.doubleValue());

		/**
		 * ②BigDecimalコンストラクタの引数にDouble.toString()を使用
		 */
		d1bd = new BigDecimal(Double.toString(d1));
		r1bd = d1bd.multiply(i1bd);
		System.out.println("【修正案2】r1bd="+r1bd.doubleValue());

		/**
		 * ③BigDecimalコンストラクタを使用せずにBigDecimal.valueOf()を使用する・
		 * BigDecimalコンストラクタの引数に文字列を使用する場合と同じ
		 */
		d1bd = BigDecimal.valueOf(d1);
		r1bd = d1bd.multiply(i1bd);
		System.out.println("【修正案3】r1bd="+r1bd.doubleValue());
	}
}

検証結果は次の通り。

d1=558.3
i1=100
単純にdoubleの積だと数値誤差が発生する場合がある
【バグ】d1*i1=55829.99999999999
BigDecimalを使用してみる
【バグ】r1bd=55829.99999999999
【修正案1】r1bd=55830.0
【修正案2】r1bd=55830.0
【修正案3】r1bd=55830.0

下記ブログが非常に参考になりました。
◎参考ブログ
floatをもとにBigDecimalオブジェクトを作成する