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


152

私が知っている0.1小数点数が有限進数(と正確に表現できない説明)ので、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ように示されるのでしょうか。


7
あなたが本当にそれを研究しているなら、あなたはそれを理解できると確信していますが、浮動小数点には「驚き」が満載されており、時には驚異的に見た方が良い場合があります。
Hot Licks

3
あなたはこれについて落ち着いた方法で考えています。浮動小数点演算は決して数学ではありません。
Jakob 2014年

13
@HotLicksは非常に間違った態度です。
ホブ、2014年

2
@RussellBorogoveは、最適化されたsumとしても、ループが実際に実行された場合と同じ最終値を持っている場合にのみ有効な最適化になります。C ++標準では、これは「as-ifルール」または「同じ観察可能な動作」と呼ばれます。
ホブ、2014年

7
@Jakobはまったく真実ではありません。浮動小数点演算は厳密に定義されており、エラー範囲などの数学的処理が優れています。それは、多くのプログラマーが分析を実行する気がないか、または「浮動小数点は不正確である」だけが知っているすべてであり、分析は気にする価値がないと誤って信じているだけです。
ホブ、2014年

回答:


155

丸め誤差はランダムではなく、その実装方法は誤差を最小限に抑えようとします。つまり、エラーが表示されない場合や、エラーがない場合があります。

たとえば、0.1正確には0.1ienew 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.81.0エラーに廃棄されます。

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

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

この例は、Java 6でMath.round(0.49999999999999994)が1を返す理由です。この場合、計算でビットが失われると、答えに大きな違いが生じます。


1
これはどこに実装されていますか?
EpicPandaForce 2014

16
@Zhuinden CPUはIEEE-754標準に準拠しています。Javaは、基盤となるCPU命令へのアクセスを提供し、関与しません。en.wikipedia.org/wiki/IEEE_floating_point
Peter Lawrey '30 / 09/30

10
@PeterLawrey:必ずしもCPUではありません。CPUに浮動小数点がない(および使用中の個別のFPUがない)マシンでは、IEEE演算はソフトウェアによって実行されます。また、ホストCPUに浮動小数点があるが、IEEE要件に準拠していない場合、そのCPUのJava実装もソフトフロートを使用する義務があると思います...
R .. GitHub STOP HELPING ICE

1
@R ..この場合、strictfp Timeを使用して固定小数点整数を検討した場合、どうなるかわかりません。(またはBigDecimal)
Peter Lawrey 2014

2
@eugeneの重要な問題は、浮動小数点が表すことができる値が限られていることです。この制限により、情報が失われる可能性があり、数が増えるにつれてエラーが失われる可能性があります。これは丸めを使用しますが、この場合は切り捨てられるため、0.1がやや大きすぎるためにやや大きすぎる数値が正しい値になります。正確に0.5
Peter Lawrey 2014年

47

浮動小数点でのオーバーフローを禁止するとx + x + x、正確に丸められた(つまり、最も近い)浮動小数点数が実際の3 * xx + x + x + x正確xになり、正確に4 * になり、x + x + x + x + x再び5 *の正しく丸められた浮動小数点近似になりますx

の最初の結果は、正確でx + x + xあるという事実から派生していx + xます。x + x + xしたがって、1回の丸めの結果です。

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

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


2番目の結果は、なぜ0.1 + 0.1 + 0.1 + 0.1正確に浮動小数点数であるかを説明します0.4。有理数1/10と4/10は、浮動小数点に変換すると、同じ相対誤差で同じように近似されます。これらの浮動小数点数の比率はちょうど4です。ことを第一と第三の結果を示す0.1 + 0.1 + 0.1とは0.1 + 0.1 + 0.1 + 0.1 + 0.1、それ自体で、それらは唯一のそれぞれに結果を関連付ける、ナイーブ誤差解析によって推論されるかもしれないより少ないエラーを有すると予想することができるが、3 * 0.15 * 0.1近いが必ずしも同じになることが期待されることができます、0.30.5

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


1
最初の行では、x + x + xは正確に正しいと言っていますが、問題の例からはそうではありません。
Alboz 2014

2
@アルボズそれx + x + xは正確に実際の3 *に正しく丸められた浮動小数点数xです。「正しく丸められた」とは、この文脈では「最も近い」を意味します。
Pascal Cuoq 2014

4
+1これは受け入れられた答えであるはずです。実際には、漠然とした一般性ではなく、何が起こっているのかを説明/証明します。
R .. GitHub ICE HELPING ICEを停止する

1
@Alboz(これらはすべて質問で想定されています)。しかし、この答えが説明するのは、エラーが最悪の場合に合計するのではなく、偶然にキャンセルされる方法です。
ホブ、2014年

1
@chebus 0.1は、16進数の0x1.999999999999999999999…p-4(数字の無限シーケンス)です。倍精度で0x1.99999ap-4として概算されます。0.2は0x1.999999999999999999999…16進数でp-3です。0.1が0x1.99999ap-4として概算されるのと同じ理由で、0.2は0x1.99999ap-3として概算されます。一方、0x1.99999ap-3も0x1.99999ap-4 + 0x1.99999ap-4です。
Pascal Cuoq 2016年

-1

浮動小数点システムは、丸めの精度を少し上げるなど、さまざまな魔法をかけます。したがって、0.1の不正確な表現による非常に小さなエラーは、最終的に0.5に四捨五入されます。

浮動小数点は、数値を表現するための優れた、しかし不正確な方法であると考えてください。考えられるすべての数値がコンピューターで簡単に表されるわけではありません。PIのような無理数。またはSQRT(2)のように。(シンボリック数学システムはそれらを表すことができますが、私は「簡単に」言いました。)

浮動小数点値は非常に近いかもしれませんが、正確ではありません。非常に接近しているため、冥王星に移動してミリメートル単位で離れることができます。しかし、数学的な意味ではまだ正確ではありません。

概算ではなく正確である必要がある場合は、浮動小数点を使用しないでください。たとえば、会計アプリケーションは、アカウント内の特定の数のペニーを正確に追跡したいと考えています。整数は正確であるため、これには適しています。整数で監視する必要がある主な問題はオーバーフローです。

通貨にBigDecimalを使用するとうまくいきます。これは、大きな表現ではありますが、基になる表現が整数であるためです。

浮動小数点数は不正確であることを認識していても、それらには多くの用途があります。ナビゲーションのための座標系またはグラフィックスシステムでの座標。天文学的な価値。科学的価値。(野球の正確な質量はとにかく電子の質量の範囲内であるとは限らないので、不正確さは問題になりません。)

アプリケーション(アカウンティングを含む)をカウントするには、整数を使用します。ゲートを通過する人数をカウントするには、intまたはlongを使用します。


2
質問には[java]というタグが付いています。Java言語の定義には、「少数の余分な精度」に対する規定はなく、少数の余分な指数ビットに対してのみ規定されてます(これは、を使用しない場合のみですstrictfp)。あなたが何かを理解することを断念したからといって、それが計り知れないものであったり、他の人がそれを理解することを断念したりする必要はありません。Java実装が言語定義を実装するために使用する長さの例として、stackoverflow.comstrictfp
/ questions / 18496560を
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.