2
0

More than 3 years have passed since last update.

Java double型の誤差とBigDecimal

Posted at

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
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0