floatとdoubleは誤差が発生する可能性がある
以下のプログラムを実行してみる。
double result = 0;
double d = 0.1;
// 0.1を10回足し算
for (int i = 1; i <= 10; i++) {
result += d;
}
System.out.println(result);
0.9999999999999999
期待値:0.1
実行結果:0.9999999999999999
誤差が発生する理由
コンピュータの内部では、2進数で数値を保持している。
例えば、
10進数の「0.5」は2進数で「0.1」
10進数の「0.1」は2進数で「0.000110011001100…」⇒無限小数となり終わらない。
よって、10進数の「0.5」は誤差なくdouble型で表現できるが、10進数の「0.1」はコンピュータのビットが有限のため正確に表現できない。
誤差の解決方法
BigDecimalを使用する。
BigDecimal result = new BigDecimal("0");
BigDecimal addValue = new BigDecimal("0.1");
// 0.1を10回足し算
for (int i = 1; i <= 10; i++) {
result = result.add(addValue);
}
System.out.println(result);
1.0
BigDecimalで誤差が発生しない理由
BigDecimalでは、内部的には整数×10のマイナス乗で値を保持している。
$ unscaledValue×10^{-scale} $
例えば、「0.3」であれば、「$ 3×10^{-1} $」
よって、二進数にしても誤差が発生しない。
その他色々な例
以下のプログラムを実行してみる。
double d = 0.1;
System.out.println(d);
0.1
上記は誤差が発生していないように見えるが、
コンピュータ内部では誤差が発生しており、コンソール表示時に「0.1」とうまく表示されただけ。
↓BigDecimalを使用すると、誤差が発生していることがわかる。
BigDecimal b = new BigDecimal(0.1); //←ここでdouble型になっているため誤差が発生している。
System.out.println(b);
0.1000000000000000055511151231257827021181583404541015625
↓StringでBigDecimalインスタンスを生成すれば、誤差は発生しない。
BigDecimal b = new BigDecimal("0.1"); //←Stringを使用すると誤差が発生しない。
System.out.println(b);
0.1
↓「0.5」は2進数で無限小数ではないため、double型でBigDecimalインスタンスを生成しても誤差が発生しない。
BigDecimal b = new BigDecimal(0.5);
System.out.println(b);
0.5
↓普通に表示しただけで誤差が見える数値もある。
double d = 1.0000000003000003;
System.out.println(d);
1.0000000003000002
doubleとBigDecimalのどちらを使用するか
金額計算など、誤差が発生してはいけないシステムの処理ではBigDecimalを使用する必要がある。
ただし、計算のスピードはdoubleの方が速いため、誤差を許容できる処理であればdoubleを使用すれば良い。
おまけ
BigDecimal.valueOfを使用すれば、double型を変換しても誤差が発生しないという勘違いが多い。
double d = 0.1;
BigDecimal b = BigDecimal.valueOf(d);
System.out.println(b);
0.1
上記の例は問題ないが、BigDecimal.valueOfは内部処理として数値をDouble.toStringしているだけである。
よって、以下のような場合は誤差が表示される。
double d = 1.0000000003000003;
BigDecimal b = BigDecimal.valueOf(d);
System.out.println(b);
1.0000000003000002