web-dev-qa-db-ja.com

浮動小数点演算は壊れていますか?

次のコードを見てください。

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

なぜこれらの不正確さが起こるのですか?

2599
Cato Johnston

バイナリ 浮動小数点 数学はこのようなものです。ほとんどのプログラミング言語では、 IEEE 754規格 に基づいています。 JavaScriptは64ビット浮動小数点表現を使用します。これはJavaのdoubleと同じです。問題の核心は、数がこの形式で整数倍の2のべき乗として表されることです。分母が2のべき乗ではない有理数(0.11/10など)は正確に表すことができません。

標準の0.1フォーマットのbinary64の場合、表現は次のように書くことができます。

これとは対照的に、有理数0.11/10であり、正確につぎのように書くことができます。

  • 10進数の0.1、または
  • C99の16進数表記法に類似した0x1.99999999999999...p-4。ここで、...は9の終わりのないシーケンスを表します。

あなたのプログラムの定数0.20.3もまた、それらの真の値に近似するでしょう。 0.2に最も近いdoubleは有理数0.2よりも大きいが、0.3に最も近いdoubleは有理数0.3よりも小さいことが起こります。 0.10.2の合計は、有理数0.3よりも大きいため、コード内の定数と一致しません。

浮動小数点演算問題のかなり包括的な扱いは、 すべてのコンピュータ科学者が浮動小数点演算について知っておくべきこと です。より読みやすい説明については、 floating-point-gui.de を参照してください。

1962
Brian R. Bondy

ハードウェア設計者の視点

浮動小数点ハードウェアを設計および構築するため、ハードウェア設計者の視点をこれに追加する必要があると思います。エラーの原因を知ることは、ソフトウェアで何が起こっているのかを理解するのに役立ちます。最終的に、これが浮動小数点エラーが発生し、時間とともに蓄積するように見える理由の説明に役立つことを願っています。

1。概要

エンジニアリングの観点から見ると、ほとんどの浮動小数点演算にはエラー要素があります。これは、浮動小数点計算を行うハードウェアが最後の1ユニットの半分未満のエラーしか必要としないためです。したがって、多くのハードウェアは、単一操作の最後の場所で1ユニットの半分未満のエラーを生成するのに必要な精度で停止しますが、これはフローティングで特に問題がありますポイント分割。単一の操作を構成するものは、ユニットが使用するオペランドの数によって異なります。ほとんどの場合、2つですが、一部のユニットは3つ以上のオペランドを取ります。このため、エラーが時間の経過とともに増加するため、操作を繰り返しても望ましいエラーが発生するという保証はありません。

2.標準

ほとんどのプロセッサは IEEE-754 標準に従いますが、一部のプロセッサは非正規化または異なる標準を使用します。たとえば、IEEE-754には非正規化モードがあり、精度を犠牲にして非常に小さな浮動小数点数を表現できます。ただし、以下では、標準的な動作モードであるIEEE-754の正規化モードについて説明します。

IEEE-754標準では、ハードウェア設計者は、最後の場所の1ユニットの半分未満である限り、エラー/イプシロンの値を許可され、結果は最後の1ユニットの半分未満でなければなりません1つの操作のための場所。これは、繰り返し操作があるときにエラーが増える理由を説明しています。 IEEE-754倍精度の場合、これは54ビットです。53ビットが浮動小数点数の仮数部とも呼ばれる(正規化された)数値部分を表すために使用されるためです(例:5.3e5の5.3)。次のセクションでは、さまざまな浮動小数点演算でのハードウェアエラーの原因について詳しく説明します。

3.除算の丸め誤差の原因

浮動小数点除算のエラーの主な原因は、商の計算に使用される除算アルゴリズムです。ほとんどのコンピューターシステムは、主にZ=X/YZ = X * (1/Y)で、逆数による乗算を使用して除算を計算します。除算は繰り返し計算されます。つまり、各サイクルは、希望する精度に達するまで商の一部のビットを計算します。これは、IEEE-754の場合、最後の場所に1ユニット未満のエラーがあるものです。 Yの逆数のテーブル(1/Y)は低速除算の商選択テーブル(QST)として知られており、商選択テーブルのビット単位のサイズは通常、基数の幅、またはビット数各反復で計算された商、およびいくつかのガードビット。 IEEE-754規格の倍精度(64ビット)の場合、それは除算器の基数のサイズに加えて、いくつかのガードビットk(k>=2)です。そのため、たとえば、商の2ビットを一度に計算する除算器の典型的な商選択テーブル(基数4)は、2+2= 4ビット(およびいくつかのオプションビット)になります。

3.1除算丸め誤差:逆数の近似

商選択テーブルの逆数は 除算方法 に依存します:SRT除算などの低速除算、またはGoldschmidt除算などの高速除算。各エントリは、可能な限り最小のエラーを生成するために、除算アルゴリズムに従って変更されます。ただし、いずれの場合も、すべての逆数は実際の逆数の近似値であり、エラーの要素を導入します。低速除算と高速除算の両方の方法で商が反復的に計算されます。つまり、商のビット数が各ステップで計算され、結果が被除数から減算され、エラーが半分未満になるまで除算器がステップを繰り返します最後の場所のユニット。低速除算法は、各ステップで商の固定桁数を計算し、通常は構築するのに費用がかかりません。高速除算法は、ステップごとに可変桁数を計算し、通常、構築するのに費用がかかります。除算方法の最も重要な部分は、それらのほとんどが逆数の近似による乗算の繰り返しに依存しているため、エラーが発生しやすいことです。

4.他の操作の丸めエラー:切り捨て

すべての操作で丸めエラーが発生する別の原因は、IEEE-754で許可されている最終回答の切り捨てモードが異なることです。切り捨て、ゼロへの丸め、 最近傍への丸め(デフォルト)、 切り捨て、切り上げがあります。すべてのメソッドは、1つの操作の最後の場所に1ユニット未満のエラー要素を導入します。時間の経過と操作の繰り返しに伴い、切り捨ても結果のエラーに累積的に追加されます。この切り捨てエラーは、累乗法で特に問題となります。累乗法には、何らかの形で繰り返される乗算が含まれます。

5.繰り返し操作

浮動小数点計算を行うハードウェアは、1つの操作の最後の場所で1ユニットの半分未満のエラーの結果を生成する必要があるだけなので、監視しないとエラーは繰り返しの操作で大きくなります。これは、境界エラーを必要とする計算では、数学者がIEEE-754の最近傍への丸め 偶数の最後の桁 を使用するなどの方法を使用する理由です。相互にキャンセルされる可能性が高く、 区間演算IEEE 754丸めモード のバリエーションと組み合わせて丸め誤差を予測し、修正します。 IEEE-754のデフォルトの丸めモードは、他の丸めモードと比較して相対誤差が小さいため(最後の場所で)最も近い偶数桁に丸めます。

デフォルトの丸めモードである最近傍への丸め 最後の桁は偶数 は、1つの操作に対して最後の桁の半分未満のエラーを保証することに注意してください。切り捨て、切り上げ、切り捨てを単独で使用すると、最後の場所では1ユニットの半分を超え、最後の場所では1ユニット未満のエラーが発生する可能性があるため、これらのモードは、区間演算で使用されます。

6.まとめ

要するに、浮動小数点演算のエラーの根本的な理由は、ハードウェアの切り捨てと、除算の場合の逆数の切り捨ての組み合わせです。 IEEE-754規格では、1回の操作の最後の場所で1ユニットの半分未満のエラーしか必要としないため、修正しない限り、繰り返し操作での浮動小数点エラーが加算されます。

570
KernelPanik

1または1/10を2進数(2進数)に変換すると、10進数で1/3を表現しようとするのと同じように、小数点の後に繰り返しパターンが表示されます。値は正確ではないため、できません。通常の浮動小数点法を使った正確な数学.

416
Joel Coehoorn

ここでの回答のほとんどは、非常にドライで技術的な用語でこの質問に対処しています。私は、普通の人間が理解できる用語でこれに対処したいと思います。

ピザをスライスしようとしていると想像してください。ピザのスライスを正確に半分にカットできるロボット式ピザカッターがあります。ピザ全体を半分にすることも、既存のスライスを半分にすることもできますが、いずれにしても、半分は常に正確です。

そのピザカッターには非常に細かい動きがあり、ピザ全体から始めて半分にし、毎回最小のスライスを半分にし続けると、半分53回スライスが高精度の能力でさえ小さすぎる前。その時点では、その非常に薄いスライスを半分にすることはできませんが、そのまま含めるか除外する必要があります。

さて、ピザの10分の1(0.1)または5分の1(0.2)になるように、すべてのスライスをどのようにつなげますか?本当に考えて、それを試してみてください。神話上の正確なピザカッターを手元に置いていれば、本物のピザを使用することもできます。 :-)


もちろん、ほとんどの経験豊富なプログラマーは本当の答えを知っています。つまり、それらを使用してピザの10分の1または5分のexactをつなぎ合わせる方法はありません。どれだけ細かくスライスしてもかなり良い近似を行うことができ、0.1の近似と0.2の近似を合計すると、0.3のかなり良い近似が得られますが、それでもやはり近似です。

倍精度の数値(ピザを53倍にできる精度)の場合、0.1の直ぐ下の数値は0.09999999999999999167332731531132594682276248931884765625と0.1000000000000000055511151231257827021181583404541015625です。後者は前者よりも0.1にかなり近いため、0.1の入力が与えられた場合、数値パーサーは後者を優先します。

(これらの2つの数値の違いは、「上向きのバイアスを導入する」または「下向きのバイアスを導入する除外」のいずれかを決定する必要がある「最小スライス」です。その最小スライスの専門用語は lp 。)

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です。


追伸一部のプログラミング言語では、 スライスを正確な10分の1に分割 できるピザカッターも提供されています。このようなピザカッターは一般的ではありませんが、1つにアクセスできる場合は、スライスの1/10または5分の1を正確に取得できることが重要である場合に使用してください。

(元々Quoraに投稿されました。)

281

浮動小数点丸め誤差素数の5が足りないため、基数2の0.1は基数10の場合のように正確に表すことができません。 0.1は、基数2では無限の桁数を取りますが、基数10では使用しません。そしてコンピュータは無限のメモリを持っていません。

203

他の正しい答えに加えて、浮動小数点演算に関する問題を回避するために、値をスケーリングすることを検討することをお勧めします。

例えば:

var result = 1.0 + 2.0;     // result === 3.0 returns true

... の代わりに:

var result = 0.1 + 0.2;     // result === 0.3 returns false

JavaScriptでは式0.1 + 0.2 === 0.3falseを返しますが、幸いなことに浮動小数点での整数算術演算は正確なので、10進表現の誤差はスケーリングによって回避できます。

実用的な例として、精度が最も重要な浮動小数点問題を避けるために、1 セントの数を表す整数としてお金を処理するには:2550ドルの代わりに25.50セント。


1 ダグラス・クロックフォード: JavaScript:良い部分 :付録A - ひどい部分(105ページ)

112
Daniel Vassallo

私の答えは非常に長いので、3つのセクションに分けました。質問は浮動小数点数学に関するものなので、マシンが実際に行うことを強調しました。また、倍精度(64ビット)に特化していますが、引数はどの浮動小数点演算にも等しく適用されます。

プリアンブル

IEEE 754倍精度2進浮動小数点形式(binary64) numberは、形式の数を表します

値=(-1)^ s *(1.m51m50... m2m1m2 * 2e-1023

64ビットで:

  • 最初のビットは 符号ビット :数値が負の場合は1、それ以外の場合は0です。1
  • 次の11ビットは exponent です。これは offset 1023です。つまり、倍精度数から指数ビットを読み取った後、取得するには1023を減算する必要があります。 2の力。
  • 残りの52ビットは significand (または仮数)です。仮数では、「暗黙の」1.は常に2 バイナリ値の最上位ビットは1であるため、省略されました。

1 -IEEE 754では、 signed zero の概念が許可されています。-+0-0の扱いは異なります。1 / (+0)は正の無限大です。 1 / (-0)は負の無限大です。値がゼロの場合、仮数ビットと指数ビットはすべてゼロです。注:ゼロ値(+0および-0)は明示的に非正規として分類されません2

2 -これは、ゼロのオフセット指数(および暗黙の0.)を持つ 非正規数 の場合ではありません。非正規の倍精度数の範囲はdです ≤| x | ≤d最大、 ここで、D (表現可能な最小の非ゼロ数)は2-1023-51 (≈4.94 * 10-324)およびd最大 (仮数が完全に1sで構成される最大の非正規数)は2です-1023 + 1 -2-1023-51 (≈2.225 * 10-308)。


倍精度数をバイナリに変換する

倍精度浮動小数点数をバイナリに変換する多くのオンラインコンバーターが存在します(例: 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の繰り返し数字で構成されることに注意してください。これは、計算にエラーがある理由のkeyです-0.1、0.2、0.3はバイナリで表現できません正確に1/9、1/3または1/7を超えるfinite数のバイナリビットは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増やし、小数点(binary)ポイントをシフトして取得します。

sum = 2^-2  * 1.0011001100110011001100110011001100110011001100110011(1)
    = 2^-54 * 5404319552844595.5 = 0.3000000000000000166533453693773481063544750213623046875

現在、仮数部には53ビットがあります(53行目は上の行の角括弧で囲まれています)。 IEEE 754のデフォルトの 丸めモード は 'Round to Nearest'-つまり、数値xの場合2つの値abの間にある場合、最下位ビットがゼロの値が選択されます。

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

abは最後のビットのみが異なることに注意してください。 ...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によるバイナリ表現とのみ異なる-54

0.1および0.2のバイナリ表現は、IEEE 754で許容される数値の最も正確な表現です。これらの表現を追加すると、デフォルトの丸めモードにより、最下位ビットのみが異なります。

TL; DR

0.1 + 0.2をIEEE 754バイナリ表現(コロンで3つの部分を区切る)で記述し、それを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.20.3を与える方法です。最後の数ビットは丸められます。

97
Wai Ha Lee

コンピュータに格納されている浮動小数点数は2つの部分、整数と基数が取られて整数部分で乗算される指数部で構成されています。

コンピューターが10進数で機能していた場合、0.11 x 10⁻¹0.22 x 10⁻¹、および0.33 x 10⁻¹になります。整数演算は簡単で正確なので、0.1 + 0.2を追加すると明らかに0.3になります。

0.51 x 2⁻¹0.251 x 2⁻²であり、それらを追加すると3 x 2⁻²または0.75になります。その通りです。

問題は、基数10では表現できますが、基数2では表現できない数値です。これらの数値は、最も近いものに丸める必要があります。非常に一般的なIEEE 64ビット浮動小数点フォーマットを想定すると、0.1に最も近い数は3602879701896397 x 2⁻⁵⁵であり、0.2に最も近い数は7205759403792794 x 2⁻⁵⁵です。それらを一緒に追加すると、10808639105689191 x 2⁻⁵⁵、または正確な10進値0.3000000000000000444089209850062616169452667236328125が得られます。浮動小数点数は一般に表示のために丸められます。

52
Mark Ransom

浮動小数点丸め誤差From すべてのコンピュータ科学者が浮動小数点演算について知っておくべきこと

無限大の実数を有限のビット数に圧縮するには、近似表現が必要です。無限に多くの整数がありますが、ほとんどのプログラムで整数計算の結果は32ビットで格納することができます。対照的に、任意の固定数のビットが与えられると、実数を用いたほとんどの計算は、その多数のビットを使用して正確に表すことができない量を生成するだろう。したがって、浮動小数点計算の結果は、その有限表現に収まるように丸める必要があります。この丸め誤差は、浮動小数点計算の特徴です。

43
Brett Daniel

私の回避策:

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

precision は、加算中に小数点の後に保存する桁数を表します。

30
Justineo

たくさんの良い答えが投稿されていますが、もう1つ追加したいと思います。

floats / doubles ですべての数値を表すことができるわけではありません。たとえば、IEEE754浮動小数点規格では、数値 "0.2"は単精度で "0.200000003"として表されます。

ボンネットの下の店舗実数のモデルは、以下のように浮動小数点数を表します。

enter image description here

0.2は簡単に入力できますが、FLT_RADIXDBL_RADIXは2です。 「2進浮動小数点演算のためのIEEE標準(ISO/IEEE Std 754-1985)」を使用するFPUを備えたコンピューターの場合は10ではありません。

そのような数を正確に表すのは少し難しいです。たとえあなたが中間の計算なしでこの変数を明示的に指定したとしても。

27
bruziuz

いいえ、壊れていませんが、ほとんどの小数部は概算する必要があります

概要

浮動小数点演算is正確ですが、残念ながら、通常の10進数の表現とはうまく一致しないため、多くの場合、書いたものからわずかに外れた入力です。

0.01、0.02、0.03、0.04 ... 0.24のような単純な数値でさえ、2進小数として正確には表現できません。 0.01、.02、.03をカウントすると、0.25に達するまでbaseで表現可能な最初の分数が得られません2。 FPを使用してこれを試した場合、0.01はわずかにずれていたため、25をニースの正確な0.25まで加算する唯一の方法は、ガードビットと丸めを含む因果関係の長いチェーンを必要としました。予測するのは難しいので、手を上げて「FP is inactact」、と言いますが、実際はそうではありません。

FPハードウェアには、ベース10では単純に見えるが、ベース2では繰り返し分数になるようなものを常に提供しています。

これはどうやって起こったのですか?

10進数で記述する場合、すべての小数部(具体的には、すべての終了10進数)は形式の有理数です

a /(2n x 5m

バイナリでは、2のみを取得しますn用語、つまり:

a/2n

そのため、10進数では表現できません 1/3。基数10には2が素因数として含まれているため、2進小数またはとして記述できるすべての数値は10進小数として記述できます。ただし、ベースとして記述することはほとんどありません10 分数はバイナリで表現できます。 0.01、0.02、0.03 ... 0.99の範囲では、3つの数値のみをFP形式で表すことができます:0.25 、0.50、および0.75。これらは1/4、1/2、および3/4であるため、すべて2のみを使用する素因数を持つ数値n 期間。

ベースで10 表現できない 1/3。しかし、バイナリでは、できません 1/10 または 1/3

したがって、すべての2進小数は10進数で記述できますが、その逆は当てはまりません。実際、ほとんどの小数部はバイナリで繰り返されます。

それに対処する

開発者は通常、<epsilon比較を行うように指示されていますが、より良いアドバイスは整数値に丸めることです(Cライブラリの場合:round()およびroundf() 、つまりFP形式のままにして、比較します。特定の小数部の長さに丸めることにより、出力に関するほとんどの問題が解決されます。

また、実数計算の問題(FPが初期の恐ろしく高価なコンピューターで発明された問題)で、宇宙の物理定数と他のすべての測定値は、比較的少数の重要な数字にしかわからない、とにかく問題空間全体は「不正確」でした。 FP「精度」は、この種のアプリケーションでは問題ではありません。

全体の問題は、人々がBeanカウントにFPを使用しようとすると本当に起こります。それは機能しますが、整数値に固執する場合にのみ、それはそれを使用するポイントを打ち負かします。 これが、これらすべての小数ソフトウェアライブラリがある理由です。

Chris によるピザの回答が大好きです。これは、「不正確さ」についての通常の手を振るだけでなく、実際の問題を説明しているためです。 FPが単に「不正確」だった場合、fixであり、数十年前にそれを行っていたはずです。 FP形式がコンパクトで高速であり、多くの数値を処理するための最良の方法だからです。また、宇宙時代や軍拡競争と、小さなメモリシステムを使用する非常に遅いコンピューターでの大きな問題を解決しようとする初期の試みからの遺産です。 (場合によっては、1ビットストレージ用の個別の磁気コアが、それは 別の話

結論

銀行で豆を数えるだけなら、そもそも10進文字列表現を使用するソフトウェアソリューションは完璧に機能します。しかし、量子色力学や空気力学をそのように行うことはできません。

26
DigitalRoss

この有名な倍精度問題に関するいくつかの統計。

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%)。

25
Kostas Chalkias

あなたはダクトテープソリューションを試しましたか?

エラーが発生した時点を判断し、短いifステートメントを使用してそれらを修正するようにしてください。それはきれいではありませんが、いくつかの問題ではこれが唯一の解決策です。

 if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
                    else { return n * 0.1 + 0.000000000000001 ;}    

C#の科学シミュレーションプロジェクトでも同じ問題がありました。バタフライ効果を無視した場合、大きな太ったドラゴンに変わってa **に噛み付くことになるでしょう。

18
workoverflow

これらの奇妙な数字は、私たちが10進法(10進法)を使うのに対して、計算のためにコンピューターが2進法(2進法)を使うために現れます。

2進数、10進数、またはその両方で正確に表現できない小数の大部分があります。結果 - 切り上げられた(しかし正確な)数結果。

15
Piyush S528

この質問の多くの重複の多くは、特定の数値に対する浮動小数点丸めの影響について尋ねています。実際には、単にそれを読むことよりも、興味のある計算の正確な結果を見ることによってそれがどのように機能するかについて感覚を得ることはより簡単です。 JavaでfloatまたはdoubleBigDecimalに変換するなど、その方法を提供する言語もあります。

これは言語にとらわれない質問なので、 Decimal to Floating-Point Converter などの言語にとらわれないツールが必要です。

二重に扱われて、問題の数にそれを適用する:

0.1は0.10000000000000000000055511151231257827021181583404541015625に変換します。

0.2は0.200000000000000011102230246251565404236316680908203125に変換されます。

0.3が0.299999999999999988897769753748434595763683319091796875に変換され、

0.30000000000000004は0.3000000000000000444089209850062616169452667236328125に変換されます。

最初の2つの数字を手動で、または Full Precision Calculator のような10進数計算で追加すると、実際の入力の正確な合計が0.3000000000000000166533453693773481063544750213623046875であることがわかります。

0.3に相当する値に丸められた場合、丸め誤差は0.0000000000000000277555756156289135105907917022705078125になります。 0.30000000000000000004に相当する値に丸めると、丸め誤差0.0000000000000000277555756156289135105907917022705078125も得られます。ラウンドトゥイーブンタイブレーカーが適用されます。

浮動小数点コンバーターに戻ると、0.30000000000000004の生の16進数は3fd3333333333334です。これは偶数桁で終わるため、正しい結果です。

13

追加できますか。人々はこれをコンピュータの問題だと常に思っていますが、もしあなたがあなたの手で数えているなら(base 10)、あなたが0.333 ...を追加する無限大を持っていない限り(1/3+1/3=2/3)=trueを得ることはできません...基数2の(1/10+2/10)!==3/10問題では、それを0.333 + 0.333 = 0.666に切り捨て、おそらく0.667に丸めますが、これは技術的に不正確なこともあります。

3進数で数えても、3分の1でも問題ありません。片手に15本の指を持つ人種では、なぜ10進数の数学が壊れたのかと聞かれるかもしれません。

13
user1641172

誰もこれについて言及していないことを考えると...

PythonやJavaなどの一部の高水準言語には、2進浮動小数点の制限を克服するためのツールが付属しています。例えば:

  • Pythonの decimalモジュール およびJavaの BigDecimalクラス 。これは、(バイナリ表記ではなく)内部で10進表記で数値を表します。どちらも精度が限られているため、まだエラーが発生しやすいですが、バイナリ浮動小数点演算に関する最も一般的な問題は解決されています。

    お金を扱うとき小数は非常にいいです: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進浮動小数点演算よりも正確な結果が得られる場合があります。

これらの解決策のどちらも完璧ではありません(特にパフォーマンスを見る場合や非常に高い精度が必要な場合)が、それでもバイナリ浮動小数点演算に関する多数の問題を解決します。

12

最良の解決策 を提供するために、私は以下の方法を発見したと言えるでしょう。

parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3

それがなぜ最良の解決策であるかを説明させてください。他の人が上の回答で述べたように、問題を解決するためにJavascriptのtoFixed()関数を使う準備をするのは良い考えです。しかし、おそらくあなたはいくつかの問題に遭遇するでしょう。

0.20.7のように2つの浮動小数点数を足し合わせるとします。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の後にそれらの不要なゼロをどうするつもりですか?あなたが望むようにそれを作るためにそれをfloatに変換する時が来ました:

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はまったく同じように見えますが、まったく機能しないことに注意してください。入力フロートを正確な出力フロートに変換する関数として適用できるので、私は最初の解決策を好みます。

12
Mohammad lm71

デジタルコンピュータに実装できる種類の浮動小数点演算は、必ず実数の近似とそれらに対する演算を使用します。 ( standard バージョンは50ページを超える文書で構成されており、その正誤表とさらなる改良に対処するための委員会があります。)

この近似は、異なる種類の近似の混合です。それぞれの近似は、正確さからの逸脱の特定の方法のために、無視することも慎重に考慮することもできます。それはまた、ハードウェアとソフトウェアの両方のレベルで、気づかないふりをしながらほとんどの人が過去を歩き回る、いくつかの明白な例外的なケースを含みます。

無限の精度を必要とする場合(例えば、より短いスタンドインの代わりにπを使用する場合)、代わりにシンボリック数学プログラムを書くか使用するべきです。

しかし、浮動小数点演算の値が曖昧でロジックやエラーがすぐに蓄積される可能性があり、それを可能にするための要件やテストを記述することができれば、コードは頻繁に問題を解決することができます。あなたのFPU.

9
Blair Houghton

楽しみのために、Standard C99の定義に従ってフロートの表現を試し、以下のコードを書きました。

このコードは、3つのグループに分けられた浮動小数点数のバイナリ表現を出力します。

SIGN EXPONENT FRACTION

その後、合計を出力します。十分な精度で合計すると、実際にハードウェアに存在する値が表示されます。

float x = 999...を書くと、コンパイラはその数を関数xxで表示されるビット表現に変換し、関数yyで表示される合計が指定された数と等しくなるようにします。

実際には、この合計は単なる近似値です。 999,999,999という数値の場合、コンパイラはfloatのビット表現に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);
}

これは、ハードウェアに存在するfloat値の実際の値を計算するコンソールセッションです。私は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も混乱していることも確認できます。 scalebc要素を設定することを忘れないでください。

表示される合計は、ハードウェア内部のものです。あなたがそれを計算することによって得る値はあなたが設定したスケールに依存します。 scaleファクタを15に設定しました。数学的には、無限の精度で、それは1,000,000,000であるようです。

8
alinsoar

もう1つの見方は、次のとおりです。数値を表すために64ビットが使用されます。結果として、2 ** 64 = 18,446,744,073,709,551,616を超える異なる数を正確に表すことはできません。

しかし、Mathによると、0から1までの間にはすでに無限大の小数部があります。IEE754では、これらの64ビットをNaNと+/- Infinityを加えた大きなスペースに効率的に使用するエンコードを定義数値は概算のみです。

残念ながら、0.3はギャップにあります。

5
Torsten Becker

このスレッドは現在の浮動小数点の実装に関する一般的な議論に少し分岐したので、それらの問題を修正するプロジェクトがあると付け加えたいと思います。

例えば https://posithub.org/ を見てください。これは、より少ないビットでより良い精度を提供することを約束する、posit(およびその前身のunum)と呼ばれる数タイプを紹介します。私の理解が正しければ、それは質問の中のある種の問題も解決します。かなり興味深いプロジェクトです、その背後にいる人は数学者です ジョングスタフソン博士 。全体がオープンソースで、C/C++、Python、Julia、およびC#( https://hastlayer.com/arithmetics )で実際に実装されています。

4
Piedone

たとえば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ビットに切り捨てられて0.0100110であり、これらは正確に0.0000001だけ異なります。


正確な状況は、これらの数字が通常科学的記数法で格納されているため、わずかに微妙です。したがって、たとえば、1/10を0.0001100として格納する代わりに、指数と仮数に割り当てたビット数によっては、1.10011 * 2^-4のようなものとして格納することもできます。これは、計算で得られる精度の桁数に影響します。

その結果、これらの丸め誤差のために、浮動小数点数に==を使用したくはありません。代わりに、それらの差の絶対値がある小さい固定数より小さいかどうかを確認できます。

3
Daniel McLaury

Python 3.5以降 あなたは近似等価性をテストするためにmath.isclose()関数を使うことができる:

>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False
3
nauer

別の質問は、これと重複して命名されました:

C++では、なぜcout << xの結果がxに対してデバッガが示している値と異なるのですか?

問題のxfloat変数です。

一つの例は次のようになります

float x = 9.9F;

デバッガーは9.89999962を表示し、cout操作の出力は9.9です。

答えはcoutためfloatのデフォルトの精度は6であることであることが判明したので、6桁に丸めます。

参考のために here を参照してください

2
Arkadiy

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;
        }
    },
    diff:{
        value: function(A,B){
            var prec = this.max(this.get_precision(A),this.get_precision(B));
            return +this.precision(A-B,prec);
        }
    },
    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を使用するというのが目的です。

Math.diff(0.2, 0.11) == 0.09 // true
0.2 - 0.11 == 0.09 // false

また、Math.diffとMath.sumは使用する精度を自動検出します。

Math.sumは任意の数の引数を受け入れます

2
bortunac

実はとても簡単です。あなたが(私たちのような)10を底とする系を持っているとき、それは底の素因数を使う分数を表すことができるだけです。 10の素因数は2と5です。分母はすべて10の素因数を使用するので、1/2、1/4、1/5、1/8、および1/10はすべてき​​れいに表現できます。/3、1/6、および1/7はすべて、分母が3または7の素因数を使用しているため、小数点以下の桁数が繰り返されます。2進数(または基数2)では、唯一の素数は2です。素因数として2のみを含みます。 2進数では、1/2、1/4、1/8はすべて小数としてきれいに表現されます。一方、1/5または1/10は小数点以下の桁数を繰り返すことになります。 0.1と0.2(1/10と1/5)は基数10のシステムでは小数がきれいですが、コンピューターが作動している基数2のシステムでは小数点以下が繰り返されています。これは、コンピュータの2進数(2進数)を人間が判読できる10進数に変換したときに持ち越されます。

からhttps://0.30000000000000004.com/ /

1
Vlad Agurets

これは実際には この質問 - に対する答えとして意図されていましたが、この質問の複製として閉じられました、whileこの答えをまとめていたので、ここに投稿することはできません...代わりに、ここに投稿します。


質問の概要:

ワークシートの10^-8/100010^-11Equalと評価されますが、VBAでは評価されません。

ワークシート上の数字は、科学表記法のデフォルト値です。

セルを数値形式に変更すると(Ctrl+1Number15の小数点を含む)は、次のようになります。

=10^-11 returns 0.000000000010000
=10^(-8/1000) returns 0.981747943019984

したがって、それらは間違いなく同じではありません... 1つはちょうど約ゼロであり、もう1つはちょうど約1です。

Excelは極端に小さい数を処理するようには設計されていません - 少なくとも株価のインストールではそうではありません。数値の精度を向上させるのに役立つアドインがあります。


Excelは、2進浮動小数点演算のIEEE標準( IEEE 754 )に従って設計されています。規格では、 浮動小数点数 の格納方法と計算方法を定義しています。 IEEE 754 標準は、浮動小数点数を妥当な量のスペースに格納することを可能にし、計算が比較的迅速に行われるため、広く使用されています。

固定小数点表現に対する浮動小数点の利点は、より広い範囲の値をサポートできることです。たとえば、小数点以下5桁の小数点以下3桁目の後に小数点がある固定小数点表現は数値123.3412.232.45などを表すことができますが、5桁の精度の浮動小数点表現は1.2345、12345、0.00012345を表すことができます。同様に、浮動小数点表現はまた、精度を維持しながら広範囲の大きさにわたる計算を可能にする。例えば、

img


その他の参考文献

1
ashleedawg

0.10.2、および0.3などの小数は、バイナリエンコードされた浮動小数点型では正確に表現されません。 0.10.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は、 選択されたターゲット でサポートしますが、clangは、OS/Xではサポートしません)。

0
chqrlie