次のコードを検討してください。
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
なぜこれらの不正確さが起こるのですか?
次のコードを検討してください。
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
なぜこれらの不正確さが起こるのですか?
回答:
2進浮動小数点演算は次のようになります。ほとんどのプログラミング言語では、IEEE 754標準に基づいています。問題の核心は、数値が2の累乗の整数倍としてこの形式で表されることです。(例えば、有理数0.1
であり、1/10
その分母正確に表現することができない2のべき乗ではありません)。
以下のために0.1
標準でbinary64
フォーマット、表現は正確のように記述することができます
0.1000000000000000055511151231257827021181583404541015625
10進数、または0x1.999999999999ap-4
でC99表記をhexfloat。対照的に、有理数0.1
は1/10
、次のように正確に記述できます。
0.1
10進数、または0x1.99999999999999...p-4
C99の16進浮動小数点表記のアナログで...
、9の無限のシーケンスを表します。定数0.2
と0.3
プログラム内の値も、それらの真の値の概算になります。最も近いdouble
もの0.2
は有理数よりも大きい0.2
が、最も近いdouble
もの0.3
は有理数よりも小さいことが起こり0.3
ます。合計0.1
とは、0.2
合理的な数よりも大きい巻き取る0.3
ので、あなたのコード内の定数で不同意します。
浮動小数点演算の問題のかなり包括的な取り扱いは、すべてのコンピューター科学者が浮動小数点演算について知っておくべきことです。わかりやすい説明については、floating-point-gui.deを参照してください。
サイドノート:すべての定位置(base-N)番号システムはこの問題を正確に共有しています
普通の古い10進数(基数10)の数値にも同じ問題があります。そのため、1/3のような数値は0.333333333になります...
たまたま、10進法で表現するのは簡単ですが、2進法には適合しない数値(3/10)を見つけました。それは両方の方向に(ある程度)行きます:1/16は10進数で醜い数字(0.0625)ですが、2進数では10進数で10,000番目(0.0001)のように見栄えがします**-私たちの日常生活で2進数を使用する習慣を身につければ、その数字を見て、何かを半分にしたり、それを何度も何度も繰り返したりすることで直感的に理解できるでしょう。
**もちろん、それは正確に浮動小数点数がメモリに格納される方法ではありません(それらは科学表記法の形式を使用します)。ただし、2進浮動小数点の精度エラーが発生する傾向があるという点を示しています。これは、通常使用する「実世界」の数値が10の累乗であることが多いためです。ただし、10進数のシステム日を使用しているためです。今日。これは、「7分の5」の代わりに71%と言う理由でもあります(5/7は10進数で正確に表すことができないため、71%は概算です)。
そのため、いいえ:2進浮動小数点数は壊れていません。たまたま、他のすべてのbase-N数値システムと同じくらい不完全です:)
サイドサイドノート:プログラミングでのフロートの使用
実際には、この精度の問題は、丸め関数を使用して浮動小数点数を表示する前に、必要な小数点以下の桁数に丸める必要があることを意味します。
また、同等性テストを、ある程度の許容範囲を許容する比較に置き換える必要があります。つまり、次のことを意味します。
しないでくださいif (x == y) { ... }
代わりにif (abs(x - y) < myToleranceValue) { ... }
。
ここで、abs
絶対値です。myToleranceValue
特定のアプリケーションに合わせて選択する必要があります。これは、許容できる「小刻みの部屋」の大きさ、および比較する最大数が何であるかと関係があります(精度の問題による)。 )。選択した言語の「イプシロン」スタイルの定数に注意してください。これらは許容値として使用されません。
私は浮動小数点ハードウェアを設計および構築するので、これにハードウェア設計者の視点を追加する必要があると思います。エラーの原因を知ることは、ソフトウェアで何が起こっているのかを理解するのに役立つ可能性があります。最終的に、これが浮動小数点エラーが発生する理由の説明に役立ち、時間の経過とともに蓄積していくと思います。
エンジニアリングの観点から見ると、浮動小数点演算を実行するハードウェアは、最後の1ユニットの半分未満のエラーを持つだけでよいため、ほとんどの浮動小数点演算にはいくつかのエラー要素があります。したがって、多くのハードウェアは、浮動小数点除算で特に問題となる単一の演算に対して、最後の場所で1ユニットの半分未満のエラーを生成するためにのみ必要な精度で停止します。単一の演算を構成するものは、ユニットが取るオペランドの数によって異なります。ほとんどの場合、2つですが、一部のユニットは3つ以上のオペランドを取ります。このため、エラーが時間の経過とともに増加するため、操作を繰り返しても望ましいエラーが発生するという保証はありません。
ほとんどのプロセッサはIEEE-754標準に準拠していますが、一部は非正規化された、または異なる標準を使用しています。たとえば、IEEE-754には非正規化モードがあり、精度を犠牲にして非常に小さな浮動小数点数を表現できます。ただし、以下では、典型的な動作モードであるIEEE-754の正規化モードについて説明します。
IEEE-754標準では、ハードウェア設計者は、最後の1ユニットの半分未満である限り、任意の値のエラー/イプシロンが許可され、結果は最後の1ユニットの半分未満でなければなりません。 1つの操作のための場所。これは、繰り返される操作があるときにエラーが増える理由を説明しています。IEEE-754倍精度の場合、これは54番目のビットです。53ビットは、仮数とも呼ばれる浮動小数点数の数値部分(正規化)を表すために使用されるためです(たとえば、5.3e5の5.3)。次のセクションでは、さまざまな浮動小数点演算でのハードウェアエラーの原因について詳しく説明します。
浮動小数点除算のエラーの主な原因は、商の計算に使用される除算アルゴリズムです。ほとんどのコンピュータシステムは、主にでZ=X/Y
、逆数による乗算を使用して除算を計算します。Z = X * (1/Y)
。除算は繰り返し計算されます。つまり、各サイクルは、IEEE-754の場合、最後の場所で1ユニット未満のエラーがある任意の精度に達するまで、商のいくつかのビットを計算します。Y(1 / Y)の逆数のテーブルは、除算の商選択テーブル(QST)と呼ばれ、商選択テーブルのビット単位のサイズは、通常、基数の幅またはビット数です。各反復で計算された商といくつかのガードビット。IEEE-754標準の倍精度(64ビット)の場合、除算器の基数のサイズに、いくつかのガードビットkを加えたものになりk>=2
ます。したがって、たとえば、商の2ビットを一度に計算する除算器の典型的な商選択テーブル(基数4)は、2+2= 4
ビットになります(オプションのビットがいくつか追加されます)。
3.1除算の丸め誤差:逆数の近似
商選択テーブルにある逆数は、除算方法によって異なります。SRT除算などの低速除算、またはゴールドシュミット除算などの高速除算。各エントリは、可能な限り最小のエラーを生成するために、除算アルゴリズムに従って変更されます。とにかく、すべての逆数は近似値です実際の逆数のエラーのいくつかの要素を紹介します。低速除算と高速除算の両方の方法で商を反復的に計算します。つまり、商のビット数を各ステップで計算し、結果を被除数から差し引き、エラーが1の半分未満になるまで除算器がステップを繰り返します。最後のユニット。低速除算メソッドは、各ステップで商の固定桁数を計算し、通常は構築コストが低く、高速除算メソッドはステップごとに可変桁数を計算し、通常は構築コストが高くなります。除算メソッドの最も重要な部分は、それらのほとんどが逆数の近似による繰り返し乗算に依存しているため、エラーが発生しやすいことです。
すべての操作での丸めエラーのもう1つの原因は、IEEE-754で許可されている最終回答の切り捨てのさまざまなモードです。切り捨て、ゼロに丸める、最も近い値に丸める(デフォルト)、切り捨て、切り上げがあります。すべてのメソッドは、単一の操作の最後の場所で1ユニット未満のエラーの要素を導入します。時間の経過および操作の繰り返しに伴い、切り捨てによって結果のエラーが累積的に増加します。この切り捨てエラーは、累乗で特に問題となります。これには、なんらかの形式の繰り返し乗算が含まれます。
浮動小数点計算を実行するハードウェアは、単一の操作の最後の場所で1ユニットの半分未満のエラーで結果を生成する必要があるだけなので、監視しないと、エラーは繰り返しの操作で大きくなります。これは、限られた誤差を必要とする計算で、数学者がIEEE-754の最後の場所で最も近い偶数桁に丸めるなどの方法を使用する理由です。これは、時間の経過とともに、誤差が互いに打ち消し合う可能性が高くなるためです。アウト、および区間演算のバリエーションと組み合わせるIEEE 754丸めモード丸め誤差を予測して修正します。IEEE-754のデフォルトの丸めモードは、他の丸めモードと比較して相対誤差が小さいため、(最後の場所で)最も近い偶数桁に丸められます。
デフォルトの丸めモード(最後の桁で最も近い偶数桁に丸める)では、1つの演算で最後の場所の1単位の半分未満のエラーが保証されることに注意してください。切り捨て、切り上げ、および切り捨てを単独で使用すると、最後の場所では1ユニットの半分よりも大きく、最後の場所では1ユニットよりも小さいエラーが発生する可能性があるため、これらのモードは、区間演算で使用されます。
つまり、浮動小数点演算でのエラーの根本的な理由は、ハードウェアでの切り捨てと、除算の場合の逆数の切り捨ての組み合わせです。IEEE-754標準では、1回の操作で最後の場所にある1単位の半分未満のエラーしか必要としないため、修正しない限り、繰り返される操作による浮動小数点エラーが追加されます。
.1または1/10を底2(バイナリ)に変換すると、底10で1/3を表すのと同じように、小数点の後に繰り返しパターンが得られます。値は正確ではないため、実行できません。通常の浮動小数点メソッドを使用した正確な計算。
ここでのほとんどの回答は、非常に乾燥した技術用語でこの質問に対処しています。普通の人間が理解できる言葉でこれに取り組みたいと思います。
あなたがピザをスライスしようとしていると想像してみてください。ピザのスライスを正確に半分に切断できるロボットピザカッターがあります。ピザ全体を半分にすることも、既存のスライスを半分にすることもできますが、いずれにせよ、半分にするのは常に正確です。
そのピザカッターは非常に細かい動きがあり、ピザ全体から始めて、それを半分にして、毎回最小のスライスを半分に続けると、スライスが小さすぎて高精度の能力すらできない前に、53倍に半分にすることができます。その時点で、その非常に薄いスライスを半分にすることはできなくなりますが、そのまま含めるか除外する必要があります。
では、ピザの10分の1(0.1)または5分の1(0.2)になるように、すべてのスライスをつなぎ合わせるにはどうすればよいでしょうか。本当にそれについて考えて、それを試しなさい。神話上の精密なピザカッターを手元に持っている場合は、実際のピザを使用することもできます。:-)
もちろん、経験豊富なプログラマーは本当の答えを知っています。つまり、どれだけ細かくスライスしても、これらのスライスを使用してピザの正確な 10分の1または5分の1をつなぎ合わせる方法はありません。かなり良い近似を行うことができます。0.1の近似と0.2の近似を合計すると、かなり良い0.3の近似が得られますが、それでもそれは単なる近似です。
倍精度の数値(53分の1のピザを半減できる精度)の場合、0.1のすぐ上または下の数値は0.09999999999999999167332731531132594682276248931884765625および0.1000000000000000055511151231257827021181583404541015625です。後者は前者よりも0.1にかなり近いので、数値パーサーは、0.1の入力が与えられると、後者を優先します。
(これらの2つの数値の違いは、「最小スライス」であり、上向きバイアスを導入するか、除外するか、下向きバイアスを導入するかを決定する必要があります。この最小スライスの専門用語はulpです。)
0.2の場合、数値はすべて同じで、2倍に拡大されます。ここでも、0.2よりわずかに高い値を優先します。
どちらの場合も、0.1と0.2の近似にはわずかに上向きのバイアスがあることに注意してください。これらのバイアスを十分に追加すると、それらは数値を必要なものからさらに遠ざけます。実際、0.1 + 0.2の場合、バイアスは、結果の数値が最も近い数値ではなくなるほど高くなります。 0.3に。
特に、0.1 + 0.2は実際には0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125ですが、0.3に最も近い数値は実際には0.299999999999999988897769753748434595763683319091796875です。
PS一部のプログラミング言語は、スライスを正確に1/10に分割できるピザカッターも提供しています。このようなピザカッターはめったにありませんが、1つにアクセスできる場合は、スライスの1/10または1/5を正確に取得できることが重要な場合に使用してください。
浮動小数点の丸めエラー。素因数5が欠落しているため、0.1はbase-10のようにbase-2のように正確に表すことができません。1/ 3が10進数で表すために無限の桁数をとるのと同じですが、base-3では "0.1"です。 0.1は、base-10ではなく、base-2で無限の桁数を取ります。また、コンピュータには無限のメモリはありません。
他の正しい答えに加えて、浮動小数点演算の問題を回避するために値をスケーリングすることを検討してください。
例えば:
var result = 1.0 + 2.0; // result === 3.0 returns true
... の代わりに:
var result = 0.1 + 0.2; // result === 0.3 returns false
式0.1 + 0.2 === 0.3
はfalse
JavaScriptで返されますが、幸い、浮動小数点での整数演算は正確なので、スケーリングすることで10進数表現のエラーを回避できます。
実用的な一例として、精度が最も重要である浮動小数点の問題を回避するために、それをお勧めします1セントの数を表す整数としてお金を処理する:2550
セントの代わりに25.50
ドル。
1ダグラス・クロックフォード:JavaScript:良い部分:付録A-ひどい部分(105ページ)。
私の回答はかなり長いので、3つのセクションに分けました。質問は浮動小数点数学に関するものなので、機械が実際に行うことを強調しました。また、倍精度(64ビット)に固有にしたが、引数は浮動小数点演算にも同様に適用される。
前文
AN IEEE 754倍精度バイナリ浮動小数点形式(binary64)数は、フォームの数を表します。
値=(-1)^ s *(1.m 51 m 50 ... m 2 m 1 m 0)2 * 2 e-1023
64ビット:
1
数値が負の0
場合は1、それ以外の場合は1です。1.
は常に2が省略されます1
。1 - IEEE 754は、概念を可能に署名されたゼロ - +0
と-0
異なる方法で処理されている:1 / (+0)
正の無限大です。1 / (-0)
負の無限大です。値がゼロの場合、仮数と指数ビットはすべてゼロです。注:ゼロ値(+0および-0)は、非正規2として明示的に分類されていません。
2-これは、オフセット指数がゼロの(および暗黙の)非正規数には当てはまりません0.
。デノーマル倍精度数の範囲をd 分 ≤| X | ≤D maxの D、最小(最小の表現の非ゼロの数)2れる-1023 - 51(≈4.94×10 -324)及びd maxの(仮数は、完全に構成されている最大の非正規化数、1
s)は2であり、-1023 + 1 - 2 -1023 - 51(≈2.225×10 -308)。
倍精度数を2進数に変換する
倍精度浮動小数点数をバイナリに変換するために多くのオンラインコンバーターが存在します(たとえば、binaryconvert.comで)が、ここに倍精度数のIEEE 754表現を取得するためのサンプルC#コードがあります(3つの部分をコロン(:
)で区切ります)。 :
public static string BinaryRepresentation(double value)
{
long valueInLongType = BitConverter.DoubleToInt64Bits(value);
string bits = Convert.ToString(valueInLongType, 2);
string leadingZeros = new string('0', 64 - bits.Length);
string binaryRepresentation = leadingZeros + bits;
string sign = binaryRepresentation[0].ToString();
string exponent = binaryRepresentation.Substring(1, 11);
string mantissa = binaryRepresentation.Substring(12);
return string.Format("{0}:{1}:{2}", sign, exponent, mantissa);
}
要点:元の質問
(TL; DRバージョンの場合は一番下にスキップしてください)
Cato Johnston(質問者)は、なぜ0.1 + 0.2!= 0.3なのかと尋ねました。
バイナリで記述され(3つの部分をコロンで区切って)、値のIEEE 754表現は次のとおりです。
0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010
0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010
仮数はの繰り返し桁で構成されることに注意してください0011
。これは、計算にエラーがある理由の鍵です-0.1、0.2、および0.3は、有限数のバイナリビットでは正確にバイナリで表すことができません。1/ 9、1 / 3、または1/7を超えると、10進数。
また、指数の累乗を52だけ減らし、バイナリ表現のポイントを52桁右にシフトできることにも注意してください(10 -3 * 1.23 == 10 -5 * 123のように)。これにより、バイナリ表現を、a * 2 pの形式で表す正確な値として表すことができます。ここで、「a」は整数です。
指数を10進数に変換し、オフセットを削除して、暗黙の1
(角括弧内)を再度追加すると、0.1と0.2は次のようになります。
0.1 => 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 => 2^-3 * [1].1001100110011001100110011001100110011001100110011010
or
0.1 => 2^-56 * 7205759403792794 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125
2つの数値を加算するには、指数が同じである必要があります。つまり、
0.1 => 2^-3 * 0.1100110011001100110011001100110011001100110011001101(0)
0.2 => 2^-3 * 1.1001100110011001100110011001100110011001100110011010
sum = 2^-3 * 10.0110011001100110011001100110011001100110011001100111
or
0.1 => 2^-55 * 3602879701896397 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125
sum = 2^-55 * 10808639105689191 = 0.3000000000000000166533453693773481063544750213623046875
合計が2 n * 1. {bbb} の形式ではないため、指数を1増やし、小数点(バイナリ)をシフトして取得します。
sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)
= 2^-54 * 5404319552844595.5 = 0.3000000000000000166533453693773481063544750213623046875
仮数には53ビットがあります(53番目は上の行の角括弧内にあります)。IEEE 754 のデフォルトの丸めモードは「Round to Nearest」です。つまり、数値xが2つの値aとbの間にある場合、最下位ビットがゼロである値が選択されます。
a = 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
= 2^-2 * 1.0011001100110011001100110011001100110011001100110011
x = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)
b = 2^-2 * 1.0011001100110011001100110011001100110011001100110100
= 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125
aとbは最後のビットのみが異なることに注意してください。...0011
+ 1
= ...0100
。この場合、最下位ビットがゼロの値はbなので、合計は次のようになります。
sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110100
= 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125
一方、0.3のバイナリ表現は次のとおりです。
0.3 => 2^-2 * 1.0011001100110011001100110011001100110011001100110011
= 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
これは、0.1と0.2の合計の2進数表現と2 -54だけ異なるだけです。
0.1と0.2のバイナリ表現は、IEEE 754で許容される数値の最も正確な表現です。これらの表現を追加すると、デフォルトの丸めモードにより、最下位ビットのみが異なる値になります。
TL; DR
ライティング0.1 + 0.2
(三つの部分を分離するコロンで)IEEE 754バイナリ表現にし、それを比較する0.3
(私は角括弧内の個別のビットを入れている)、これは次のとおりです。
0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100]
0.3 => 0:01111111101:0011001100110011001100110011001100110011001100110[011]
10進数に変換されたこれらの値は次のとおりです。
0.1 + 0.2 => 0.300000000000000044408920985006...
0.3 => 0.299999999999999988897769753748...
差は正確に2 -54です。これは、元の値と比較すると、(多くのアプリケーションでは)〜5.5511151231258×10 -17です。
有名な「すべてのコンピュータサイエンティストが浮動小数点演算について知っておくべきこと」(この回答のすべての主要な部分をカバーしています)を読んだ人なら誰でも知っているように、浮動小数点数の最後の数ビットを比較することは本質的に危険です。
ほとんどの電卓は、追加の使用保護桁をどのようにしている、この問題を回避するために0.1 + 0.2
与えるだろう0.3
。最後の数のビットが丸みを帯びています。
コンピュータに格納されている浮動小数点数は、整数と指数の2つの部分で構成されます。これらの基数には、基数が使用され、整数部分が乗算されます。
コンピュータがベース10で作業していた場合、0.1
だろう1 x 10⁻¹
、0.2
となり2 x 10⁻¹
、そして0.3
だろう3 x 10⁻¹
。整数演算は簡単で正確なので、追加0.1 + 0.2
すると明らかにになり0.3
ます。
コンピュータは通常、base 10では機能しません。base2では機能します。たとえば、0.5
is 1 x 2⁻¹
や0.25
is などの一部の値について正確な結果を取得1 x 2⁻²
し3 x 2⁻²
、それらを、またはに追加することができます0.75
。丁度。
問題は、基数2ではなく、基数10で正確に表すことができる数値で発生します。これらの数は、最も近い同等数に丸める必要があります。非常に一般的なIEEE 64ビット浮動小数点形式を想定すると、に最も近い数0.1
は3602879701896397 x 2⁻⁵⁵
であり、に最も近い数0.2
は7205759403792794 x 2⁻⁵⁵
です。それらを一緒に追加すると10808639105689191 x 2⁻⁵⁵
、またはの正確な10進数値になり0.3000000000000000444089209850062616169452667236328125
ます。浮動小数点数は通常、表示のために丸められます。
浮動小数点の丸めエラー。すべてのコンピューター科学者が浮動小数点演算について知っておくべきことから:
無限に多くの実数を有限数のビットに圧縮するには、近似表現が必要です。整数は無限にありますが、ほとんどのプログラムでは、整数計算の結果を32ビットに格納できます。対照的に、任意の固定ビット数の場合、実数を使用したほとんどの計算では、その数のビットを使用して正確に表現できない量が生成されます。したがって、浮動小数点計算の結果は、有限表現に戻すために丸められることがよくあります。この丸め誤差は、浮動小数点計算の特徴です。
良い答えがたくさん投稿されていますが、もう1つ追加したいと思います。
すべての数値を介して表すことができないフロート / 倍 例えば、番号「0.2」は、IEEE754浮動小数点規格に単精度に「0.200000003」として表されます。
内部で実数を格納するモデルは、浮動小数点数を次のように表します。
次のように入力することができたとしても0.2
、容易、FLT_RADIX
かつDBL_RADIX
2です。「2進浮動小数点演算のIEEE標準(ISO / IEEE Std 754-1985)」を使用するFPUを搭載したコンピューターでは10ではありません。
したがって、そのような数値を正確に表すのは少し難しいです。中間計算なしでこの変数を明示的に指定した場合でも。
この有名な倍精度の質問に関連するいくつかの統計。
0.1のステップ(0.1から100)を使用してすべての値(a + b)を追加すると、精度エラーの可能性は約15%になります。エラーにより、値がわずかに大きくなったり小さくなったりする可能性があることに注意してください。ここではいくつかの例を示します。
0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)
0.1のステップ(100から0.1)を使用してすべての値(a-bでa> b)を減算する場合、精度エラーの可能性は約34%です。ここではいくつかの例を示します。
0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)
* 15%と34%は実際に巨大であるため、精度が非常に重要な場合は常にBigDecimalを使用します。2桁の10進数(ステップ0.01)では、状況は少し悪化します(18%および36%)。
概要
浮動小数点演算は正確ですが、残念ながら、通常の10を底とする数値表現とうまく一致しないため、多くの場合、入力したものから少しずれた入力を与えていることがわかります。
0.01、0.02、0.03、0.04 ... 0.24のような単純な数値でさえ、2進分数として正確に表現できません。0.01、.02、.03 ...を数えた場合、0.25に到達するまでは、基数2で表現できる最初の小数が得られます。FPを使用してそれを試した場合、0.01はわずかにずれていたので、25を追加して正確に正確な0.25にする唯一の方法は、ガードビットと丸めを含む因果関係の長い連鎖を必要とすることになります。予測が難しいので、手を上げて「FPは不正確」と言いますが、それは本当ではありません。
FPハードウェアには、base 10では単純に見えるがbase 2では繰り返しの割合であるものを常に提供しています。
どうしてそうなった?
10進数で書く場合、すべての端数(具体的には、すべての終了10進数)は、次の形式の有理数です。
a /(2 n x 5 m)
バイナリでは、2 n項のみを取得します。つまり、
a / 2 n
したがって、10進数では、1 / 3を表すことはできません。ベース10は、素因数として2を含んでいるので、我々はバイナリ分数として記述することができ、すべての数はまた、ベース10分数のように記述することができます。ただし、10を底とする分数として記述するものはほとんどバイナリで表現できません。0.01、0.02、0.03 ... 0.99の範囲では、FP形式で表すことができるのは、0.25、0.50、および0.75の3つの数値のみです。 2 n項のみを使用する素因数を使用します。
ベース10では、1 / 3を表すことはできません。しかし、バイナリでは、我々が行うことができない1 / 10 または 1 / 3。
したがって、すべての2進数の小数は10進数で記述できますが、その逆は当てはまりません。そして実際には、ほとんどの小数はバイナリで繰り返されます。
それに対処する
開発者は通常、<イプシロン比較を行うように指示されます。整数値に丸める(Cライブラリでは、round()およびroundf()、つまりFP形式のままにする)ことをお勧めします。特定の小数部の長さに丸めると、出力に関するほとんどの問題が解決します。
また、実数処理問題(FPが初期の恐ろしく高価なコンピューターで発明された問題)では、宇宙の物理定数と他のすべての測定値は、比較的少数の有意な数値しか知らないため、問題空間全体がとにかく「不正確」だった。FPの「精度」は、この種のアプリケーションでは問題になりません。
全体の問題は、人々が豆を数えるためにFPを使用しようとするときに本当に発生します。それはそのために機能しますが、あなたが整数値に固執する場合にのみ、それはそれを使用する点を打ち負かします。これが、これらすべての小数ソフトウェアライブラリを備えている理由です。
クリスのピザの回答が大好きです。「不正確さ」についての通常の手振りだけではなく、実際の問題を説明しているからです。FPが単に「不正確」だった場合、それを修正することができ、数十年前にそれを行っていただろう。私たちがそうしていない理由は、FP形式がコンパクトで高速であり、それが多くの数値を処理する最良の方法だからです。また、それは宇宙時代と軍備競争、および小さなメモリシステムを使用する非常に遅いコンピュータでの大きな問題を解決する初期の試みからの遺産です。(1ビットストレージ用の個別の磁気コアが時々ありますが、それは別の話です。)
結論
銀行で豆を数えるだけの場合は、最初に10進数の文字列表現を使用するソフトウェアソリューションが完全に機能します。しかし、量子色力学や空気力学をそのように行うことはできません。
nextafter()
、IEEEフロートのバイナリ表現に整数のインクリメントまたはデクリメントを実装できます。また、浮動小数点数を整数として比較し、両方が負の場合を除いて正しい答えを得ることができます(符号の大きさ対2の補数のため)。
最高のソリューションを提供するために、私は次の方法を発見したと言えるでしょう:
parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3
それが最善の解決策である理由を説明しましょう。上記で述べた他の人が答えるように、問題を解決するには、すぐに使用できるJavascript toFixed()関数を使用することをお勧めします。しかし、おそらくいくつかの問題が発生します。
次のような2つの浮動小数点数0.2
を0.7
合計するとします0.2 + 0.7 = 0.8999999999999999
。
予想される結果は0.9
、この場合は1桁の精度の結果が必要であることを意味していました。したがって、使用する必要(0.2 + 0.7).tofixed(1)
がありますが、特定のパラメータをtoFixed()に指定することはできません。たとえば、指定された数値に依存するためです。
`0.22 + 0.7 = 0.9199999999999999`
この例では、2桁の精度が必要なため、それはである必要がありますtoFixed(2)
。したがって、指定されたすべての浮動小数点数に適合するためのパラメーターは何ですか?
あなたはそれをすべての状況で10にするとしましょう:
(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"
くそー!9の後でこれらの不要なゼロをどのように処理しますか?それをフロートに変換して、希望どおりに作成するときです。
parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9
ソリューションが見つかったので、次のような関数として提供することをお勧めします。
function floatify(number){
return parseFloat((number).toFixed(10));
}
自分で試してみましょう:
function floatify(number){
return parseFloat((number).toFixed(10));
}
function addUp(){
var number1 = +$("#number1").val();
var number2 = +$("#number2").val();
var unexpectedResult = number1 + number2;
var expectedResult = floatify(number1 + number2);
$("#unexpectedResult").text(unexpectedResult);
$("#expectedResult").text(expectedResult);
}
addUp();
input{
width: 50px;
}
#expectedResult{
color: green;
}
#unexpectedResult{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="number1" value="0.2" onclick="addUp()" onkeyup="addUp()"/> +
<input id="number2" value="0.7" onclick="addUp()" onkeyup="addUp()"/> =
<p>Expected Result: <span id="expectedResult"></span></p>
<p>Unexpected Result: <span id="unexpectedResult"></span></p>
次のように使用できます。
var x = 0.2 + 0.7;
floatify(x); => Result: 0.9
以下のようW3Schoolsのは、別の解決策があまりにもそこにあることを示唆している、あなたは上記の問題を解決するために乗算と除算することができます:
var x = (0.2 * 10 + 0.1 * 10) / 10; // x will be 0.3
(0.2 + 0.1) * 10 / 10
同じように見えてもまったく機能しないことに注意してください!入力フロートを正確な出力フロートに変換する関数として適用できるので、私は最初のソリューションを好みます。
この質問の多数の重複の多くは、特定の数値に対する浮動小数点の丸めの影響について尋ねています。実際には、興味のある計算の正確な結果を見るだけでなく、それを読むだけでなく、それがどのように機能するかを理解する方が簡単です。一部の言語では、Javaでのfloat
やdouble
への変換など、その方法を提供しBigDecimal
ています。
これは言語にとらわれない質問なので、10進数から浮動小数点へのコンバータなどの言語にとらわれないツールが必要です。
これを問題の数値に適用すると、ダブルとして扱われます。
0.1は0.1000000000000000055511151231257827021181583404541015625に変換され、
0.2は0.200000000000000011102230246251565404236316680908203125に変換され、
0.3は0.299999999999999988897769753748434595763683319091796875に変換され、
0.30000000000000004は0.3000000000000000444089209850062616169452667236328125に変換されます。
手動で、またはFull Precision Calculatorなどの小数計算機で最初の2つの数値を追加すると、実際の入力の正確な合計は0.3000000000000000166533453693773481063544750213623046875になります。
0.3に相当する値に切り捨てると、丸め誤差は0.0000000000000000277555756156289135105907917022705078125になります。0.30000000000000004に相当する値に切り上げると、丸め誤差0.0000000000000000277555756156289135105907917022705078125も発生します。四捨五入のタイブレーカーが適用されます。
浮動小数点コンバーターに戻ると、0.30000000000000004の生の16進数は3fd3333333333334であり、偶数の数字で終わるため、正しい結果になります。
誰もこれについて言及していないことを考えると...
PythonやJavaなどの一部の高水準言語には、バイナリ浮動小数点の制限を克服するためのツールが付属しています。例えば:
Pythonのdecimal
モジュールとJavaのBigDecimal
クラス。内部では10進表記で数値を表します(2進表記ではありません)。どちらも精度が制限されているため、エラーが発生しやすくなりますが、2進浮動小数点演算に関する最も一般的な問題は解決されます。
お金を扱う場合、小数は非常に便利です。10セント+ 20セントは常に正確に30セントです。
>>> 0.1 + 0.2 == 0.3
False
>>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
True
Pythonのdecimal
モジュールは、IEEE標準854-1987に基づいています。
Pythonのfractions
モジュールとApache CommonのBigFraction
クラス。どちらも有理数を(numerator, denominator)
ペアとして表し、10進浮動小数点演算よりも正確な結果が得られる場合があります。
これらのソリューションはどちらも完璧ではありませんが(特にパフォーマンスを調べる場合、または非常に高い精度が必要な場合)、2進浮動小数点演算の多くの問題を解決します。
追加することはできますか?人々は常にこれをコンピューターの問題であると想定しますが、手で数える場合(ベース10)、(1/3+1/3=2/3)=true
0.333 ...に0.333 ...を無限に追加しない限り(1/10+2/10)!==3/10
、ベースの問題と同様に取得できません。2、0.333 + 0.333 = 0.666に切り捨て、おそらくそれを0.667に丸めます。これも技術的に不正確です。
3進数で数えると、3分の1は問題になりません-おそらく、それぞれの手に15本の指があるいくつかのレースでは、10進数の数学が壊れた理由を尋ねるでしょう...
デジタルコンピューターで実装できる浮動小数点演算の種類は、必然的に実数の近似値とそれらに対する演算を使用します。(標準バージョンは50ページを超えるドキュメントに実行され、エラッタとさらなる改良に対処するための委員会があります。)
この近似は、さまざまな種類の近似の混合であり、正確さからの特定の逸脱方法のために、それぞれを無視するか、注意深く説明することができます。また、ほとんどの人が気づかないふりをして通り過ぎるハードウェアレベルとソフトウェアレベルの両方で、多くの明示的な例外的なケースが発生します。
無限の精度が必要な場合(たとえば、多くの短いスタンドインの1つではなく、πを使用)、代わりにシンボリック数学プログラムを作成または使用する必要があります。
しかし、浮動小数点演算の値やロジックが曖昧で、エラーがすぐに蓄積され、それを可能にするための要件とテストを記述できるという考えに問題がなければ、コードは頻繁にFPU。
ただ面白くするために、標準C99の定義に従って、フロートの表現で遊んで、以下のコードを書きました。
このコードは、フロートのバイナリ表現を3つのグループに分けて出力します
SIGN EXPONENT FRACTION
その後、合計を出力します。十分な精度で合計すると、ハードウェアに実際に存在する値が表示されます。
したがって、を記述するfloat x = 999...
と、コンパイラは、関数によって出力されるビット表現でその数を変換xx
し、関数によって出力される合計が指定された数yy
と等しくなるようにします。
実際には、この合計は概算にすぎません。数値999,999,999の場合、コンパイラーはフロートのビット表現に数値1,000,000,000を挿入します
コードの後に、コンソールセッションをアタッチします。ここでは、コンパイラーによって挿入されたハードウェアに実際に存在する両方の定数(PIと999999999を引いたもの)の項の合計を計算します。
#include <stdio.h>
#include <limits.h>
void
xx(float *x)
{
unsigned char i = sizeof(*x)*CHAR_BIT-1;
do {
switch (i) {
case 31:
printf("sign:");
break;
case 30:
printf("exponent:");
break;
case 23:
printf("fraction:");
break;
}
char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
printf("%d ", b);
} while (i--);
printf("\n");
}
void
yy(float a)
{
int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
int fraction = ((1<<23)-1)&(*(int*)&a);
int exponent = (255&((*(int*)&a)>>23))-127;
printf(sign?"positive" " ( 1+":"negative" " ( 1+");
unsigned int i = 1<<22;
unsigned int j = 1;
do {
char b=(fraction&i)!=0;
b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
} while (j++, i>>=1);
printf("*2^%d", exponent);
printf("\n");
}
void
main()
{
float x=-3.14;
float y=999999999;
printf("%lu\n", sizeof(x));
xx(&x);
xx(&y);
yy(x);
yy(y);
}
これは、ハードウェアに存在するフロートの実際の値を計算するコンソールセッションです。私が使用しbc
、メインプログラムによって出力された項の和を印刷します。その合計をpython repl
などに挿入することもできます。
-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872
それでおしまい。999999999の値は実際には
999999999.999999446351872
bc
-3.14も摂動していることを確認することもできます。でscale
係数を設定することを忘れないでくださいbc
。
表示される合計は、ハードウェアの内部です。計算によって得られる値は、設定したスケールによって異なります。scale
係数を15に設定しました。数学的には、無限の精度で、1,000,000,000のようです。
たとえば、8桁の精度で10進法で作業することを想像してください。あなたは
1/3 + 2 / 3 == 1
これが戻ることを学びますfalse
。どうして?まあ、私たちが持っている実数として
1/3 = 0.333 ....および2/3 = 0.666 ....
小数点以下8桁で切り捨てると、
0.33333333 + 0.66666666 = 0.99999999
もちろん、これはとは1.00000000
まったく異なり0.00000001
ます。
固定ビット数の2進数の状況もまったく同じです。実数として、
1/10 = 0.0001100110011001100 ...(ベース2)
そして
1/5 = 0.0011001100110011001 ...(ベース2)
これらをたとえば7ビットに切り捨てると、
0.0001100 + 0.0011001 = 0.0100101
一方、
3/10 = 0.01001100110011 ...(ベース2)
これは7ビットに切り捨てられ、is 0.0100110
であり、これらは正確に異なります0.0000001
。
これらの数値は通常、科学表記法で保存されるため、正確な状況は少し微妙です。したがって、たとえば、1/10を格納する代わりに、指数と仮数に割り当てたビット数に応じて、の0.0001100
ようなものとして格納でき1.10011 * 2^-4
ます。これは、計算で得られる精度の桁数に影響します。
要するに、これらの丸めエラーのために、浮動小数点数で==を本質的に使用したくないということです。代わりに、それらの差の絶対値が一定の小さな数値よりも小さいかどうかを確認できます。
Python 3.5以降では、math.isclose()
関数を使用して近似等価性をテストできます。
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False
このスレッドは、現在の浮動小数点実装に関する一般的な議論に少し分岐したため、問題の修正に関するプロジェクトがあると付け加えます。
たとえばhttps://posithub.org/を見てください。これは、少ないビットでより高い精度を提供することを約束するposit(およびその前身のunum)と呼ばれる数値型を示しています。私の理解が正しければ、問題の種類の問題も修正されます。非常に興味深いプロジェクトで、その背後にいる人物は数学者、ジョン・グスタフソン博士です。全体はオープンソースであり、C / C ++、Python、Julia、C#での実際の実装が多数あります(https://hastlayer.com/arithmetics)。
それは実際にはかなり簡単です。(私たちのような)基数10のシステムがある場合、それは基数の素因数を使用する分数のみを表現できます。10の素因数は2と5です。したがって、分母はすべて10の素因数を使用するため、1 / 2、1 / 4、1 / 5、1 / 8、1 / 10はすべてきれいに表現できます。対照的に、1 / 3、1 / 6、および1/7は、分母が3または7の素因数を使用するため、すべて10進数の繰り返しです。バイナリ(または基数2)では、素因数は2のみです。したがって、分数のみをきれいに表現できます。素因数として2のみを含みます。2進数では、1 / 2、1 / 4、1 / 8はすべて小数としてきれいに表現されます。一方、1/5または1/10は小数の繰り返しです。したがって、0.1と0.2(1/10と1/5)は、基数10のシステムでは10進数をクリーンにしますが、コンピューターが動作している基数2のシステムでは10進数を繰り返します。これらの繰り返し10進数で計算すると、
小数のような番号0.1
、0.2
および0.3
バイナリ形式で正確に表現されていない浮動小数点型をコードしていました。の近似値の合計は、に使用される近似値0.1
と0.2
は異なります0.3
。したがって、の誤りは0.1 + 0.2 == 0.3
、ここでより明確に見ることができます。
#include <stdio.h>
int main() {
printf("0.1 + 0.2 == 0.3 is %s\n", 0.1 + 0.2 == 0.3 ? "true" : "false");
printf("0.1 is %.23f\n", 0.1);
printf("0.2 is %.23f\n", 0.2);
printf("0.1 + 0.2 is %.23f\n", 0.1 + 0.2);
printf("0.3 is %.23f\n", 0.3);
printf("0.3 - (0.1 + 0.2) is %g\n", 0.3 - (0.1 + 0.2));
return 0;
}
出力:
0.1 + 0.2 == 0.3 is false
0.1 is 0.10000000000000000555112
0.2 is 0.20000000000000001110223
0.1 + 0.2 is 0.30000000000000004440892
0.3 is 0.29999999999999998889777
0.3 - (0.1 + 0.2) is -5.55112e-17
これらの計算をより確実に評価するには、浮動小数点値に10進数ベースの表現を使用する必要があります。C標準では、このようなタイプはデフォルトでは指定されていませんが、テクニカルレポートで説明されている拡張機能として指定されています。
_Decimal32
、_Decimal64
および_Decimal128
タイプがシステム上で利用可能であるかもしれない(例えば、GCCは、上でそれらをサポートして選択したターゲットが、クランは上でそれらをサポートしていないOS X)。
Math.sum(javascript)....演算子の置換の種類
.1 + .0001 + -.1 --> 0.00010000000000000286
Math.sum(.1 , .0001, -.1) --> 0.0001
Object.defineProperties(Math, {
sign: {
value: function (x) {
return x ? x < 0 ? -1 : 1 : 0;
}
},
precision: {
value: function (value, precision, type) {
var v = parseFloat(value),
p = Math.max(precision, 0) || 0,
t = type || 'round';
return (Math[t](v * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
}
},
scientific_to_num: { // this is from https://gist.github.com/jiggzson
value: function (num) {
//if the number is in scientific notation remove it
if (/e/i.test(num)) {
var zero = '0',
parts = String(num).toLowerCase().split('e'), //split into coeff and exponent
e = parts.pop(), //store the exponential part
l = Math.abs(e), //get the number of zeros
sign = e / l,
coeff_array = parts[0].split('.');
if (sign === -1) {
num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
} else {
var dec = coeff_array[1];
if (dec)
l = l - dec.length;
num = coeff_array.join('') + new Array(l + 1).join(zero);
}
}
return num;
}
}
get_precision: {
value: function (number) {
var arr = Math.scientific_to_num((number + "")).split(".");
return arr[1] ? arr[1].length : 0;
}
},
sum: {
value: function () {
var prec = 0, sum = 0;
for (var i = 0; i < arguments.length; i++) {
prec = this.max(prec, this.get_precision(arguments[i]));
sum += +arguments[i]; // force float to convert strings to number
}
return Math.precision(sum, prec);
}
}
});
アイデアは、浮動小数点エラーを回避するために演算子の代わりに数学を使用することです
Math.sumは使用する精度を自動検出します
Math.sumは任意の数の引数を受け入れます
次の結果を検討してください。
error = (2**53+1) - int(float(2**53+1))
>>> (2**53+1) - int(float(2**53+1))
1
2**53+1
まではすべて正常に機能するとき、ブレークポイントをはっきりと見ることができます2**53
。
>>> (2**53) - int(float(2**53))
0
これは、倍精度バイナリ:IEEE 754倍精度バイナリ浮動小数点形式:binary64が原因で発生します。
倍精度浮動小数点形式のWikipediaページから:
倍精度の2進浮動小数点は、パフォーマンスと帯域幅のコストにもかかわらず、単精度の浮動小数点よりも範囲が広いため、PCで一般的に使用される形式です。単精度浮動小数点形式と同様に、同じサイズの整数形式と比較すると、整数の精度が不足しています。これは一般的に単にdoubleとして知られています。IEEE 754標準では、binary64が次のように指定されています。
- 符号ビット:1ビット
- 指数:11ビット
- 重要な精度:53ビット(52が明示的に格納されています)
特定のバイアスされた指数と52ビットの小数部を持つ特定の64ビット倍精度データによって想定される実数値は、
または
それを私に指摘してくれた@a_guestに感謝します。
別の質問がこの質問の重複として指定されています:
C ++では、cout << x
デバッガーが表示している値と異なる結果になるのはなぜですかx
ですか?
の x
問題のは、あるfloat
変数。
一例は
float x = 9.9F;
デバッガーは9.89999962
、の出力を示しますcout
操作です9.9
。
答えはそれであることが判明 cout
のデフォルトの精度float
は6であるため、6桁の10進数に丸められます。
参照してくださいここで参考のために