1. ホーム
  2. java

[解決済み] なぜ0.1を何度も足すとロスレスのままなのか?

2022-04-28 05:15:41

質問

は知っています。 0.1 10進数は有限の2進数では正確に表現できない( 説明 ) のため double n = 0.1 は精度を失い、正確には 0.1 . 一方 0.5 であるため、正確に表現することができます。 0.5 = 1/2 = 0.1b .

というのは理解できるのですが 0.1 3回 は、正確に与えることはできません。 0.3 というわけで、次のコードは次のように表示されます。 false :

double sum = 0, d = 0.1;
for (int i = 0; i < 3; i++)
    sum += d;
System.out.println(sum == 0.3); // Prints false, OK

しかし、それならどうして 0.1 5回 は、まさに 0.5 ? 次のコードは、次のように表示します。 true :

double sum = 0, d = 0.1;
for (int i = 0; i < 5; i++)
    sum += d;
System.out.println(sum == 0.5); // Prints true, WHY?

もし 0.1 は正確に表現できないのに、どうして5回足すと正確に 0.5 というのは、正確に表すことができるのでしょうか?

どのように解決するのですか?

丸め誤差はランダムではなく、その実装方法は誤差を最小にしようとします。 つまり、誤差が見えない、あるいは誤差がない場合があります。

例えば 0.1 は正確には 0.1 すなわち new BigDecimal("0.1") < new BigDecimal(0.1) しかし 0.5 はまさに 1.0/2

このプログラムでは、関係する真の値を表示します。

BigDecimal _0_1 = new BigDecimal(0.1);
BigDecimal x = _0_1;
for(int i = 1; i <= 10; i ++) {
    System.out.println(i+" x 0.1 is "+x+", as double "+x.doubleValue());
    x = x.add(_0_1);
}

印刷物

0.1000000000000000055511151231257827021181583404541015625, as double 0.1
0.2000000000000000111022302462515654042363166809082031250, as double 0.2
0.3000000000000000166533453693773481063544750213623046875, as double 0.30000000000000004
0.4000000000000000222044604925031308084726333618164062500, as double 0.4
0.5000000000000000277555756156289135105907917022705078125, as double 0.5
0.6000000000000000333066907387546962127089500427246093750, as double 0.6000000000000001
0.7000000000000000388578058618804789148271083831787109375, as double 0.7000000000000001
0.8000000000000000444089209850062616169452667236328125000, as double 0.8
0.9000000000000000499600361081320443190634250640869140625, as double 0.9
1.0000000000000000555111512312578270211815834045410156250, as double 1.0

注:その 0.3 が少しずれていますが、その時に 0.4 の場合、53ビットの制限に合わせるためにビットを1つ下げなければならず、エラーは破棄されます。再び、エラーが 0.60.7 しかし 0.8 から 1.0 の場合、エラーは破棄されます。

5回足すとエラーがキャンセルされるのではなく、累積されるはずです。

エラーが発生する理由は、精度が53ビットと限られているためです。 つまり、数値が大きくなるにつれて、より多くのビットが使われるようになり、末尾のビットが削除されなければならなくなるのです。このため、丸め誤差が生じますが、この場合はあなたに有利に働きます。

小さい数値を取得する場合は、逆の効果が得られます。 0.1-0.0999 => 1.0000000000000286E-4 と表示され、以前より多くのエラーが表示されます。

この例として、Java6ではなぜか なぜ Math.round(0.4999999999994) は 1 を返すのでしょうか? この場合、計算におけるビットの損失は、結果として答えに大きな違いをもたらします。