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


2983

次のコードを検討してください。

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

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


127
浮動小数点変数は通常、この動作をします。それは、それらがハードウェアに格納されている方法が原因です。詳細については、浮動小数点数に関するWikipediaの記事をご覧ください。
ベンS

62
JavaScriptは小数を浮動小数点数として扱います。これは、加算などの演算が丸め誤差の影響を受ける可能性があることを意味します。この記事をご覧になるとよいでしょう:すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと
matt b

4
参考までに、JavaScriptの数値型はすべてIEEE-754 Doubleです。
ゲイリーウィロビー

6
JavaScriptはMathにIEEE 754標準を使用するため、64ビット浮動小数点数を使用します。つまり、浮動小数点(10進数)計算を実行すると、10進数が10進数であるのに、コンピューターが2進数で動作するため、精度エラーが発生します。
Pardeep Jain

回答:


2253

2進浮動小数点演算は次のようになります。ほとんどのプログラミング言語では、IEEE 754標準に基づいています。問題の核心は、数値が2の累乗の整数倍としてこの形式で表されることです。(例えば、有理数0.1であり、1/10その分母正確に表現することができない2のべき乗ではありません)。

以下のために0.1標準でbinary64フォーマット、表現は正確のように記述することができます

  • 0.1000000000000000055511151231257827021181583404541015625 10進数、または
  • 0x1.999999999999ap-4C99表記をhexfloat

対照的に、有理数0.11/10、次のように正確に記述できます。

  • 0.1 10進数、または
  • 0x1.99999999999999...p-4C99の16進浮動小数点表記のアナログで...、9の無限のシーケンスを表します。

定数0.20.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特定のアプリケーションに合わせて選択する必要があります。これは、許容できる「小刻みの部屋」の大きさ、および比較する最大数が何であるかと関係があります(精度の問題による)。 )。選択した言語の「イプシロン」スタイルの定数に注意してください。これらは許容値として使用されません


181
すべての場合に使用できる「The Epsilon」がないため、「some error constant」の方が「The Epsilon」よりも正しいと思います。異なる状況では、異なるイプシロンを使用する必要があります。そして、マシンのイプシロンは、使用するのに適した定数になることはほとんどありません。
Rotsor

34
そうではありません、非常にすべての浮動小数点演算は、IEEE [754]標準に基づいていることは事実。たとえば、古いIBM 16進FPを使用しているシステムがまだいくつかあり、IEEE-754演算をサポートしていないグラフィックカードもまだあります。しかし、それは合理的な近似に当てはまります。
Stephen Canon

19
クレイはスピードのためにIEEE-754準拠を廃止しました。Javaは、最適化としての遵守も緩めました。
アートテイラー

28
お金は量子化されるので、お金の計算は常に、常に整数の固定小数点演算で行う方法について、この答えに何かを追加する必要があると思います。(1セントのほんの一部、または最小の通貨単位で内部会計計算を行うことは理にかなっています。これは、たとえば「月額$ 29.99」を日次レートに変換するときの丸め誤差を減らすのに役立ちますが、それは依然として固定小数点演算です。)
zwol 2014年

18
興味深い事実:この非常に0.1が2進浮動小数点で正確に表されていないため、悪名高いPatriotミサイルソフトウェアのバグが発生し、最初のイラク戦争中に28人が死亡しました。
hdl

603

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

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

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/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の半分未満になるまで除算器がステップを繰り返します。最後のユニット。低速除算メソッドは、各ステップで商の固定桁数を計算し、通常は構築コストが低く、高速除算メソッドはステップごとに可変桁数を計算し、通常は構築コストが高くなります。除算メソッドの最も重要な部分は、それらのほとんどが逆数の近似による繰り返し乗算に依存しているため、エラーが発生しやすいことです。

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

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

5.繰り返される操作

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

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

6.まとめ

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


8
(3)間違いです。部門の丸め誤差は、最後の場所で1単位以上ですが、最後の場所で最大で半分の単位です。
gnasher729 2014

6
@ gnasher729良いキャッチ。ほとんどの基本的な操作でも、デフォルトのIEEE丸めモードを使用すると、最後に1ユニットの1/2未満のエラーが発生します。説明を編集し、ユーザーがデフォルトの丸めモードをオーバーライドすると、エラーが1 ulpの1/2より大きく1 ulp未満になる可能性があることも注記しました(これは特に組み込みシステムで当てはまります)。
KernelPanik 2014

39
(1)浮動小数点数にエラーはありません。すべての浮動小数点値はまさにその値です。ほとんど(すべてではない)の浮動小数点演算では、不正確な結果が得られます。たとえば、1.0 / 10.0に正確に等しい2進浮動小数点値はありません。一方、一部の操作(たとえば、1.0 + 1.0)正確な結果をもたらします。
ソロモンスロー2014年

19
「浮動小数点除算のエラーの主な原因は、商の計算に使用される除算アルゴリズムです」とは、非常に誤解を招くものです。IEEE-754準拠の除算の場合、浮動小数点除算でのエラーの唯一の原因は、結果を結果形式で正確に表現できないことです。使用されるアルゴリズムに関係なく、同じ結果が計算されます。
スティーブンキャノン

6
@マット返信が遅くなってすみません。これは基本的に、リソース/時間の問題とトレードオフが原因です。長い除算/より「通常の」除算を行う方法があります。これは、基数2のSRT除算と呼ばれます。ただし、これは被除数から除数を繰り返しシフトおよび減算し、クロックサイクルごとに商の1ビットのみを計算するため、多くのクロックサイクルを要します。サイクルあたりの商のより多くのビットを計算し、効果的なパフォーマンス/速度のトレードオフを行うことができるように、逆数のテーブルを使用します。
KernelPanik 2016

463

.1または1/10を底2(バイナリ)に変換すると、底10で1/3を表すのと同じように、小数点の後に繰り返しパターンが得られます。値は正確ではないため、実行できません。通常の浮動小数点メソッドを使用した正確な計算。


133
素晴らしく短い答え。繰り返しパターンは0.00011001100110011001100110011001100110011001100110011 ...
Konstantin Chernov

4
これは、そもそもバイナリに変換しないより良いアルゴリズムが使用されない理由を説明していません。
Dmitri Zaitsev 2016年

12
パフォーマンスだから。バイナリはマシンにネイティブであるため、バイナリを使用すると数千倍高速になります。
Joel Coehoorn、2016年

7
正確な10進数値を生成するメソッドがあります。BCD(2進化10進数)またはその他のさまざまな形式の10進数。ただし、これらはどちらも2進浮動小数点を使用するよりも遅く(LOTが遅く)、多くのストレージを必要とします。(例として、パックされたBCDは1バイトに2桁の10進数字を格納します。これは、実際には256の可能な値を格納できる1バイトの100の可能な値、または1バイトの可能な値の約60%を浪費する100/256です。)
ダンカンC

16
@Jacksonkrあなたはまだベース10で考えています。コンピューターはbase-2です。
Joel Coehoorn、2016年

307

ここでのほとんどの回答は、非常に乾燥した技術用語でこの質問に対処しています。普通の人間が理解できる言葉でこれに取り組みたいと思います。

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

そのピザカッターは非常に細かい動きがあり、ピザ全体から始めて、それを半分にして、毎回最小のスライスを半分に続けると、スライスが小さすぎて高精度の能力すらできない前に、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を正確に取得できることが重要な場合に使用してください。

(元はQuoraに投稿されています。)


3
正確な数学を含むいくつかの言語があることに注意してください。1つの例は、たとえばGNU Guileを介したSchemeです。draketo.de/english/exact-math-to-the-rescueを参照してください—これらは数学を分数として保持し、最終的にのみスライスします。
Arne Babenhauserheide

5
@FloatingRock実際、合理的な数値が組み込まれている主流のプログラミング言語はほとんどありません。私と同じように、ArneはSchemerなので、これは私たちが台無しにしてしまうものです。
Chris Jester-Young

5
@ArneBabenhauserheideこれは有理数でのみ機能することを追加する価値があると思います。したがって、piのような無理数を使っていくつかの計算をしている場合は、それをpiの倍数として保存する必要があります。もちろん、piを含む計算は、正確な10進数として表すことはできません。
アイディアカピ2015年

13
@connexoわかりました。36度になるようにピザ回転機をどのようにプログラムしますか?36度とは何ですか?(ヒント:正確な方法でこれを定義できる場合は、スライス、つまり10分の1のピザカッターもあります。)つまり、実際には1/360(度)または1 / 2進浮動小数点のみで10(36度)。
Chris Jester-Young

12
@connexoまた、「すべての馬鹿」はピザを正確に 36度回転させることはできません。人間はエラーが発生しやすく、非常に正確なことは何もできません。
Chris Jester-Young

212

浮動小数点の丸めエラー。素因数5が欠落しているため、0.1はbase-10のようにbase-2のように正確に表すことができません。1/ 3が10進数で表すために無限の桁数をとるのと同じですが、base-3では "0.1"です。 0.1は、base-10ではなく、base-2で無限の桁数を取ります。また、コンピュータには無限のメモリはありません。


133
コンピュータは、0.1 + 0.2 = 0.3を得るために無限のメモリを必要としません
Pacerier

23
@Pacerier確かに、2つの制限のない精度の整数を使用して分数を表すか、引用符表記を使用できます。これを不可能にするのは、「2進数」または「10進数」の特定の概念です。つまり、一連の2進数/ 10進数と、そこに基数ポイントがあるという考えです。正確な合理的な結果を得るには、より良いフォーマットが必要です。
Devin Jeanpierre、2011年

15
@Pacerier:2進浮動小数点も10進浮動小数点も、1/3または1/13を正確に格納できません。10進浮動小数点型はM / 10 ^ E形式の値を正確に表すことができますが、他のほとんどの分数を表す場合、同じサイズの2進浮動小数点数ほど正確ではありません。多くのアプリケーションでは、いくつかの「特別な」もので完全な精度を持つよりも、任意の分数でより高い精度を持つ方が便利です。
スーパーキャット2014

13
彼らは@Pacerier 行う彼らは答えのポイントだったバイナリ浮動小数点数として数字を格納している場合。
マークアメリー2014

3
@chux:2進数型と10進数型の精度の違いはそれほど大きくありませんが、10進数型のベストケース精度と最悪ケース精度の10:1の違いは、バイナリ型の2:1の違いよりもはるかに大きくなります。ハードウェアにもソフトウェアにも効率的な実装ができないように見えるため、どちらの10進数タイプでも効率的に動作するようにハードウェアを作成したか、ソフトウェアを作成したかは知りません。
スーパーキャット2015

121

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

例えば:

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.3falseJavaScriptで返されますが、幸い、浮動小数点での整数演算は正確なので、スケーリングすることで10進数表現のエラーを回避できます。

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


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


3
問題は、変換自体が不正確であることです。16.08 * 100 = 1607.9999999999998。数値を分割して個別に変換する必要がありますか(16 * 100 + 08 = 1608など)?
Jason

38
ここでの解決策は、すべての計算を整数で行い、比率(この場合は100)で除算し、データを表示する場合にのみ丸めることです。これにより、計算は常に正確になります。
David Granado

16
ちょっとひねりを加えるだけです:整数演算は、ある点までの浮動小数点でのみ正確です(意図されたしゃれ)。数値が0x1p53より大きい場合(Java 7の16進浮動小数点表記、9007199254740992を使用する場合)、その時点でulpは2であるため、0x1p53 + 1は0x1p53に切り捨てられます(0x1p53 + 3は0x1p53 +に切り上げられます+ 4、四捨五入のため)。:-Dしかし、確かに、あなたの数が9兆未満であれば、大丈夫です。:-P
クリスジェスター-ヤング

2
ジェイソン、結果を丸めればいい(int)(16.08 * 100 + 0.5)
ミハイル・セメノフ2015

@CodyBugstein " では、どのようにして.1 + .2で.3を表示するのですか? "小数点を配置するカスタム印刷関数を記述します
RonJohn

113

私の回答はかなり長いので、3つのセクションに分けました。質問は浮動小数点数学に関するものなので、機械が実際に行うことを強調しました。また、倍精度(64ビット)に固有にしたが、引数は浮動小数点演算にも同様に適用される。

前文

AN IEEE 754倍精度バイナリ浮動小数点形式(binary64)数は、フォームの数を表します。

値=(-1)^ s *(1.m 51 m 50 ... m 2 m 1 m 02 * 2 e-1023

64ビット:

  • 最初のビットは符号ビットです1数値が負の0場合は1、それ以外の場合は1です。
  • 次の11ビットは指数であり、これは1023 だけオフセットされています。つまり、倍精度数から指数ビットを読み取った後、2の累乗を得るために1023を減算する必要があります。
  • 残りの52ビットが仮数(又は仮数)。仮数部では、バイナリ値の最上位ビットがであるため、「暗黙的」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の(仮数は、完全に構成されている最大の非正規化数、1s)は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つの値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進数表現と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。最後の数のビットが丸みを帯びています。


14
私の回答は投稿した直後に反対票が投じられました。それ以来、多くの変更を加えてきました(0.1と0.2をバイナリで書き込むときに繰り返し発生するビットを明示的に示すことも含めて、オリジナルでは省略しました)。反対投票者がこれを見る機会がありましたら、私が私の答えを改善できるように、私にいくつかのフィードバックをお願いできますか?IEEE 754での合計の扱いが他の回答では同じようにカバーされていないため、私の回答には何か新しいものが追加されていると思います。「すべてのコンピュータサイエンティストが知っておくべきこと...」は同じ内容をカバーしていますが、私の答えは特に 0.1 + 0.2の場合を扱っています。
Wai Ha Lee

57

コンピュータに格納されている浮動小数点数は、整数と指数の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.5is 1 x 2⁻¹0.25is などの一部の値について正確な結果を取得1 x 2⁻²3 x 2⁻²、それらを、またはに追加することができます0.75。丁度。

問題は、基数2ではなく、基数10で正確に表すことができる数値で発生します。これらの数は、最も近い同等数に丸める必要があります。非常に一般的なIEEE 64ビット浮動小数点形式を想定すると、に最も近い数0.13602879701896397 x 2⁻⁵⁵であり、に最も近い数0.27205759403792794 x 2⁻⁵⁵です。それらを一緒に追加すると10808639105689191 x 2⁻⁵⁵、またはの正確な10進数値になり0.3000000000000000444089209850062616169452667236328125ます。浮動小数点数は通常、表示のために丸められます。


2
@Markこの明確な説明をありがとうございましたが、0.1 + 0.4が正確に0.5になるのはなぜですか(少なくともPython 3では)。また、Python 3で浮動小数点数を使用しているときに同等性をチェックするための最良の方法は何ですか?
pchegoor

2
@ user2417881 IEEE浮動小数点演算にはすべての演算に丸め規則があり、2つの数値が少しずれていても、丸めによって正確な答えが得られる場合があります。詳細はコメントには長すぎますが、私はとにかくそれらの専門家ではありません。この回答でわかるように、0.5は2進数で表すことができる数十進数の1つですが、それは単なる偶然です。同等性テストについては、stackoverflow.com / questions / 5595425 /…を参照してください。
Mark Ransom

1
@ user2417881あなたの質問に興味をそそられたので、私はそれを完全な質問と回答に変えました:stackoverflow.com/q/48374522/5987
Mark Ransom

47

浮動小数点の丸めエラー。すべてのコンピューター科学者が浮動小数点演算について知っておくべきことから:

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


33

私の回避策:

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

精度とは、加算時に小数点の後に保持する桁数を指します。


30

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

すべての数値を介して表すことができないフロート / 例えば、番号「0.2」は、IEEE754浮動小数点規格に単精度に「0.200000003」として表されます。

内部で実数を格納するモデルは、浮動小数点数を次のように表します。

ここに画像の説明を入力してください

次のように入力することができたとしても0.2、容易、FLT_RADIXかつDBL_RADIX2です。「2進浮動小数点演算のIEEE標準(ISO / IEEE Std 754-1985)」を使用するFPUを搭載したコンピューターでは10ではありません。

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


28

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

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


28

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

概要

浮動小数点演算正確ですが、残念ながら、通常の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進数の文字列表現を使用するソフトウェアソリューションが完全に機能します。しかし、量子色力学や空気力学をそのように行うことはできません。


最も近い整数への丸めは、すべての場合において比較問題を解決する安全な方法ではありません。0.4999998と0.500001は異なる整数に丸めるため、すべての丸めのカットポイントの周りに「危険ゾーン」があります。(これらの10進文字列は、IEEEバイナリフロートとして正確に表現できない可能性があります。)
Peter Cordes

1
また、浮動小数点は「レガシー」フォーマットですが、非常によく設計されています。今、再設計すれば誰もが何を変えるのかは知りません。私はそれについて学ぶほど、それは本当によくデザインされていると思います。たとえば、バイアス指数は、連続するバイナリフロートが連続する整数表現を持っていることを意味するためnextafter()、IEEEフロートのバイナリ表現に整数のインクリメントまたはデクリメントを実装できます。また、浮動小数点数を整数として比較し、両方が負の場合を除いて正しい答えを得ることができます(符号の大きさ対2の補数のため)。
Peter Cordes

私は同意しません、浮動小数点数はバイナリではなく小数として保存する必要があり、すべての問題が解決されます。
Ronen Festinger 2017

" x /(2 ^ n + 5 ^ n) "は " x /(2 ^ n * 5 ^ n) "になるべきではありませんか?
Wai Ha Lee

@RonenFestinger-1/3はどうですか?
スティーブンC

19

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

エラーがいつ発生したかを特定し、短いifステートメントで修正してみてください。これはきれいではありませんが、問題によってはこれが唯一の解決策であり、これはその1つです。

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

私はc#の科学シミュレーションプロジェクトでも同じ問題を抱えていました。バタフライ効果を無視すると、大きな太ったドラゴンに変わり、a **


19

最高のソリューションを提供するために、私は次の方法を発見したと言えるでしょう:

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

それが最善の解決策である理由を説明しましょう。上記で述べた他の人が答えるように、問題を解決するには、すぐに使用できるJavascript toFixed()関数を使用することをお勧めします。しかし、おそらくいくつかの問題が発生します。

次のような2つの浮動小数点数0.20.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同じように見えてもまったく機能しないことに注意してください!入力フロートを正確な出力フロートに変換する関数として適用できるので、私は最初のソリューションを好みます。


これは本当に頭痛の種になりました。12個の浮動小数点数を合計し、合計とそれらの数字の場合は平均を表示します。toFixed()を使用すると、2つの数値の合計が修正される可能性がありますが、複数の数値を合計する場合、飛躍は重要です。
Nuryagdy Mustapayev

@Nuryagdy Mustapayev 12の浮動小数点数を合計する前にテストしたので、私はあなたの意図を知りませんでした。結果に対してfloatify()関数を使用し、それに対して必要なことをすべて実行しました。使用しても問題はありませんでした。
Mohammad

私が言っているのは、約20のパラメーターと20の数式があり、各数式の結果が他の数式に依存している状況では、この解決策は役に立ちませんでした。
Nuryagdy Mustapayev

16

これらの奇妙な数字が表示されるのは、コンピューターが計算のために2進数(基数2)の数体系を使用するのに対し、10進数(基数10)を使用するためです。

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


私はあなたの第2段落をまったく理解していません。
Nae、

1
「画分の大半は、小数点のいずれかで正確に表現できないよう@Nae私は2番目の段落を翻訳でしょう。彼らはまだビット数に正確になりますが、/表現に固有の数字-バイナリだから、ほとんどの結果は四捨五入されます使用されています。"
スティーブサミット

15

この質問の多数の重複の多くは、特定の数値に対する浮動小数点の丸めの影響について尋ねています。実際には、興味のある計算の正確な結果を見るだけでなく、それを読むだけでなく、それがどのように機能するかを理解する方が簡単です。一部の言語では、Javaでのfloatdoubleへの変換など、その方法を提供し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であり、偶数の数字で終わるため、正しい結果になります。


2
編集をロールバックした人には、コードを引用するのに適切なコード引用を検討します。この回答は、言語に中立であるため、引用コードはまったく含まれていません。数字は英文で使用でき、コードに変換されません。
パトリシアシャナハン2017年

これはおそらく、誰かがあなたの数値をコードとしてフォーマットした理由です-フォーマットのためではなく、読みやすさのためです。
Wai Ha Lee

...また、丸め10進表記ではなく2進表記を指します。これまたは、たとえば、これを参照してください。
Wai Ha Lee

@WaiHaLee 10進数には奇数/偶数テストを適用せず、16進数のみを適用しました。16進数字は、その2進展開の最下位ビットがゼロである場合に限ります。
パトリシアシャナハン

14

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

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進浮動小数点演算の多くの問題を解決します。


14

追加することはできますか?人々は常にこれをコンピューターの問題であると想定しますが、手で数える場合(ベース10)、(1/3+1/3=2/3)=true0.333 ...に0.333 ...を無限に追加しない限り(1/10+2/10)!==3/10、ベースの問題と同様に取得できません。2、0.333 + 0.333 = 0.666に切り捨て、おそらくそれを0.667に丸めます。これも技術的に不正確です。

3進数で数えると、3分の1は問題になりません-おそらく、それぞれの手に15本の指があるいくつかのレースでは、10進数の数学が壊れた理由を尋ねるでしょう...


人間は10進数を使用しているため、フロートがデフォルトで10進数として表されない理由はわかりません。正確な結果を得るためです。
Ronen Festinger 2017

人間は..「良い理由は、」あなたは、単にすべてのベースですべての割合を表す傾けることです。..バイナリは、我々はコンピューティングのためのほとんどを使用一つである、ベース10以外の多くの拠点(小数)を使用する

@RonenFestingerのバイナリ演算は、数字を使った8つの基本演算しか必要としないため、コンピュータに実装するのは簡単です。たとえば$ a $、$ b $を$ 0,1 $と言うと、知る必要があるのは$ \ operatorname {xor}(a、b)だけです。 $と$ \ operatorname {cb}(a、b)$、xorは排他的論理和、またはcbは「キャリービット」であり、$ a = 1 = b $の場合を除き、すべての場合で$ 0 $です。 1つ(実際には、すべての操作の互換性により$ 2 $のケースが節約され、必要なのは$ 6 $のルールのみです)。10進展開では、10倍の10倍(10進表記)のケースを格納する必要があり、ビットごとに10ドルの異なる状態があり、キャリーでストレージを無駄にします。
Oskar Limka

@RonenFestinger-Decimalの方が正確ではありません。それがこの答えが言っていることです。選択したすべての基数について、無限に繰り返される数字列を与える有理数(小数)があります。記録のために、最初のコンピュータのいくつかは、やった使用ベースに番号のための10個の表現を、しかし、先駆的なコンピュータのハードウェア設計者は、すぐにベース2を実装するために非常に簡単に、より効率的であったと結論付けました。
スティーブンC

9

デジタルコンピューターで実装できる浮動小数点演算の種類は、必然的に実数の近似値とそれらに対する演算を使用します。(標準バージョンは50ページを超えるドキュメントに実行され、エラッタとさらなる改良に対処するための委員会があります。)

この近似は、さまざまな種類の近似の混合であり、正確さからの特定の逸脱方法のために、それぞれを無視するか、注意深く説明することができます。また、ほとんどの人が気づかないふりをして通り過ぎるハードウェアレベルとソフトウェアレベルの両方で、多くの明示的な例外的なケースが発生します。

無限の精度が必要な場合(たとえば、多くの短いスタンドインの1つではなく、πを使用)、代わりにシンボリック数学プログラムを作成または使用する必要があります。

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


9

ただ面白くするために、標準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のようです。


5

これを見る別の方法:数値を表すために64ビットが使用されます。その結果、2 ** 64 = 18,446,744,073,709,551,616を超えることはできず、さまざまな数値を正確に表すことができます。

ただし、Mathによると、0と1の間の小数はすでに無限に多いと言われています。数値のみ概算。

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


4

たとえば、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ます。これは、計算で得られる精度の桁数に影響します。

要するに、これらの丸めエラーのために、浮動小数点数で==を本質的に使用したくないということです。代わりに、それらの差の絶対値が一定の小さな数値よりも小さいかどうかを確認できます。


4

Python 3.5以降では、math.isclose()関数を使用して近似等価性をテストできます。

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

3

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

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


3

それは実際にはかなり簡単です。(私たちのような)基数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進数で計算すると、

https://0.30000000000000004.com/から


3

小数のような番号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は、上でそれらをサポートして選択したターゲットが、クランは上でそれらをサポートしていないOS X)。


1

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は任意の数の引数を受け入れます


1
なぜこれらの不正確さが起こるのか」という質問にあなたが答えたかどうかはわかりません
ワイハリー

ある意味あなたは正しいですが、私はこの問題に関してjavascriptの奇妙な動作からここに来ました...私は一種の解決策を共有したいだけです
bortunac

あなたはまだ質問に答えてません。
ワイハリー

あなたはこれで問題を抱えているkは...どこを移動したり、あなたが主張する場合、私はちょうどそれを削除することができますを教えて
bortunac

0

私は浮動小数点の周りにこの興味深い問題を見た:

次の結果を検討してください。

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に感謝します。


-1

別の質問がこの質問の重複として指定されています:

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

x問題のは、あるfloat変数。

一例は

float x = 9.9F;

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

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

参照してくださいここで参考のために


1
IMO-これをここに投稿するのは間違ったアプローチでした。私はそれがイライラしていることを知っていますが、元の質問への回答が必要な人々(どうやら今は削除されたようです!)はここでそれを見つけません。作業を保存する価値があると本当に感じた場合は、1)これが実際に回答する別のQを探す、2)自己回答式の質問を作成することをお勧めします。
スティーブンC
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.