web-dev-qa-db-ja.com

0.1を複数回追加しても損失がないのはなぜですか?

0.1 10進数は有限の2進数で正確に表現できないことを知っています( 説明 )。したがって、double n = 0.1は精度を失い、正確に0.1になりません。 。一方、0.50.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.1five timesを追加すると、正確に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が得られますか?

146
icza

丸め誤差はランダムではなく、実装方法によって誤差を最小限に抑えようとします。これは、エラーが表示されないか、エラーがない場合があることを意味します。

たとえば、_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.6_と_0.7_のエラーは徐々に戻りますが、_0.8_から_1.0_のエラーは破棄されます。

5回追加すると、エラーはキャンセルされずに累積されます。

エラーが発生する理由は、精度が限られているためです。すなわち53ビット。これは、数値が大きくなるにつれてより多くのビットを使用するため、ビットを最後から落とす必要があることを意味します。これにより丸めが行われますが、この場合は有利です。
小さい数値を取得すると、逆の効果が得られます。 _0.1-0.0999_ => _1.0000000000000286E-4_と、以前よりも多くのエラーが表示されます。

この例は、in Java 6 Math.round(0.49999999999999994)が1を返す理由 の場合です。答えに。

150
Peter Lawrey

オーバーフローを除いて、浮動小数点では、x + x + xは実際の3 * _xに正確に丸められた(つまり最も近い)浮動小数点数であり、x + x + x + xは4 * _xであり、x + x + x + x + xここでも、5 * xの正しく丸められた浮動小数点近似です。

x + x + xの最初の結果は、x + xが正確であるという事実に由来します。したがって、x + x + xは、1つの丸めのみの結果です。

2番目の結果はより難しく、その1つのデモンストレーションについて説明します here (そしてStephen Canonはxの最後の3桁のケース分析によって別の証明を暗示します)。要約すると、3 * xは2 * xと同じ binade であるか、4 * xと同じビネードであり、いずれの場合も3番目の追加でのエラーが2番目の追加でのエラーをキャンセルすることを推測することができます(最初の追加は既に述べたとおり正確です)。

3番目の結果「x + x + x + x + xは正しく丸められます」は、最初の結果がx + xの正確さから導出されるのと同じ方法で、2番目から導出されます。


2番目の結果は、0.1 + 0.1 + 0.1 + 0.1が正確に浮動小数点数0.4である理由を説明しています。有理数1/10と4/10は、浮動小数点に変換したときに同じ相対誤差で同じ方法で近似されます。ポイント。これらの浮動小数点数の比率は、正確に4です。 1番目と3番目の結果は、0.1 + 0.1 + 0.10.1 + 0.1 + 0.1 + 0.1 + 0.1のエラーは、単純なエラー分析で推測されるよりも少ないと予想されることを示していますが、それ自体は、結果をそれぞれ3 * 0.1および5 * 0.1。これらは近いと予想されますが、0.3および0.5と必ずしも同一ではありません。

4回目の追加後に0.1を追加し続けると、最終的に「0.1がn回自身に追加された」という丸めエラーがn * 0.1から分岐し、n/10からさらに分岐します。 。 「0.1自体の値をn回追加」の値をnの関数としてプロットすると、バイネードによって一定の勾配の線が観察されます(n番目の追加の結果が特定のバイネードに分類されるとすぐに、加算のプロパティは、同じバイネードで結果を生成した以前の加算と同様であることが期待できます。同じバイナリー内で、エラーは拡大または縮小します。 binadeからbinadeへの傾斜のシーケンスを見ると、しばらくの間、バイナリの0.1の繰り返し数字を認識するでしょう。その後、吸収が始まり、曲線は平坦になります。

46
Pascal Cuoq