web-dev-qa-db-ja.com

浮動小数点の丸めエラーの原因は何ですか?

浮動小数点演算には精度の問題があることを知っています。私は通常、数値の固定10進数表現に切り替えるか、単にエラーを無視することで、それらを克服します。

しかし、私はこの不正確さの原因が何であるかわかりません。浮動小数点数で丸めの問題が非常に多いのはなぜですか?

66
nmat

これは、一部の分数は、丸めずに表現するために非常に大きな(または無限の)場所を必要とするためです。これは、2進数またはその他の10進数表記と同じです。計算に使用する小数点以下の桁数を制限する場合(および分数表記での計算を避ける場合)、単純な式でさえ1/3 + 1/3として丸める必要があります。結果として2/3と書く代わりに、0.33333 + 0.33333 = 0.66666と書く必要がありますが、これは2/3とは異なります。

コンピュータの場合、桁数はメモリとCPUレジスタの技術的な性質によって制限されます。内部で使用されるバイナリ表記は、さらにいくつかの困難を追加します。コンピュータは通常、分数表記で数値を表現できませんが、一部のプログラミング言語ではこの機能が追加されているため、これらの問題をある程度回避することができます。

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

84

thorsten には決定的な link があります。追加します:

表現の形式によっては、いくつかの数値に対して丸め誤差が生じます。 IEEE浮動小数点数または10進数で1/3を表現してみてください。どちらも正確に行うことはできません。これはあなたの質問に答えるだけではありませんが、私はこの経験則をうまく使いました:

  • ユーザーが入力した値を10進数で格納します(ほとんどの場合、10進数で入力したため、バイナリまたは16進数を使用するユーザーはほとんどいません)。これにより、ユーザーが入力した正確な表現が常に得られます。
  • ユーザーが入力した分数を保存する必要がある場合は、分子と分母(これも10進数)を保存します。
  • 同じ数量の複数の測定単位(摂氏/華氏など)を備えたシステムがあり、ユーザーが両方を入力できる場合は、入力した値と入力した単位を保存します。変換して保存しないでください。精度/正確性を失うことなく実行できる場合を除いて、単一の表現。保存された値およびの単位をすべての計算で使用します。
  • 機械で生成された値をIEEE浮動小数点に格納します(これは、A/Dコンバーターを備えたアナログセンサーなどの電子測定デバイスによって生成された数値、または丸められていない計算結果です)。これは、シリアル接続を介してセンサーを読み取り、すでに10進形式(18.2 Cなど)で値を提供している場合には適用されないことに注意してください。
  • ユーザーが表示できる合計などを(銀行口座の残高のように)10進数で保存します。適切に丸めますが、その値を将来のすべての計算の最終的な値として使用します。
12
Scott Whitlock

これまでに言及されていないように見えるのは、不安定なアルゴリズム悪条件の問題の概念です。前者について最初に説明します。これは、初心者の数値論者にとってはより頻繁な落とし穴のようです。

(逆数)黄金比の累乗の計算を考えてみましょう_φ=0.61803…_;これを実行する1つの可能な方法は、_φ^0=1_と_φ^1=φ_で始まる再帰式φ^n=φ^(n-2)-φ^(n-1)を使用することです。この再帰をお気に入りのコンピューティング環境で実行し、その結果を正確に評価された能力と比較すると、重要な数値がゆっくりと浸食されていることがわかります。 Mathematicaでの例を次に示します。

_ph = N[1/GoldenRatio];  
Nest[Append[#1, #1[[-2]] - #1[[-1]]] & , {1, ph}, 50] - ph^Range[0, 51]  
{0., 0., 1.1102230246251565*^-16, -5.551115123125783*^-17, 2.220446049250313*^-16, 
-2.3592239273284576*^-16, 4.85722573273506*^-16, -7.147060721024445*^-16, 
1.2073675392798577*^-15, -1.916869440954372*^-15, 3.1259717037102064*^-15, 
-5.0411064211886014*^-15, 8.16837916750579*^-15, -1.3209051907825398*^-14, 
2.1377864756200182*^-14, -3.458669982359108*^-14, 5.596472721011714*^-14, 
-9.055131861349097*^-14, 1.465160458236081*^-13, -2.370673237795176*^-13, 
3.835834102607072*^-13, -6.206507137114341*^-13, 1.004234127360273*^-12, 
-1.6248848342954435*^-12, 2.6291189633497825*^-12, -4.254003796798193*^-12, 
6.883122762265558*^-12, -1.1137126558640235*^-11, 1.8020249321541067*^-11, 
-2.9157375879969544*^-11, 4.717762520172237*^-11, -7.633500108148015*^-11, 
1.23512626283229*^-10, -1.9984762736468268*^-10, 3.233602536479646*^-10, 
-5.232078810126407*^-10, 8.465681346606119*^-10, -1.3697760156732426*^-9, 
2.216344150333856*^-9, -3.5861201660070964*^-9, 5.802464316340953*^-9, 
-9.388584482348049*^-9, 1.5191048798689004*^-8, -2.457963328103705*^-8, 
3.9770682079726053*^-8, -6.43503153607631*^-8, 1.0412099744048916*^-7, 
-1.6847131280125227*^-7, 2.725923102417414*^-7, -4.4106362304299367*^-7, 
7.136559332847351*^-7, -1.1547195563277288*^-6}
_

_φ^41_の意図された結果には間違った符号があり、さらに以前には、_φ^39_の計算値と実際の値は共通の数字を共有していません(_3.484899258054952_ * ^-9 _for the computed version against the true value_ 7.071019424062048_*^-9_)。したがって、アルゴリズムは不安定であり、この再帰式を不正確な算術で使用しないでください。これは、再帰式の固有の性質によるものです。この再帰には「減衰」および「増大」するソリューションがあり、代替の「増大」ソリューションが物乞いしているときに、フォワードソリューションによって「減衰」ソリューションを計算しようとしています。数値的な悲しみのために。したがって、彼/彼女の数値アルゴリズムが安定していることを確認する必要があります。

悪条件問題の概念について:数値的に何かを行うための安定した方法があるかもしれませんが、あなたが持っている問題はあなたのアルゴリズムでは解決できないことが非常によくあるかもしれません。これは問題自体のせいであり、解決方法ではありません。数値の標準的な例は、いわゆる「ヒルベルト行列」を含む線形方程式の解です。

Hilbert matrix

行列は悪条件行列の標準的な例です。大きなヒルベルト行列でシステムを解こうとすると、不正確な解が返される場合があります。

これがMathematicaのデモです:正確な算術の結果を比較します

_Table[LinearSolve[HilbertMatrix[n], HilbertMatrix[n].ConstantArray[1, n]], {n, 2, 12}]
{{1, 1}, {1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 
  1}, {1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1,
   1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 
  1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}
_

そして不正確な算術

_Table[LinearSolve[N[HilbertMatrix[n]], N[HilbertMatrix[n].ConstantArray[1, n]]], {n, 2, 12}]
{{1., 1.}, {1., 1., 1.}, {1., 1., 1., 1.}, {1., 1., 1., 1., 1.},  
  {1., 1., 1., 1., 1., 1.}, {1., 1., 1., 1., 1., 1., 1.}, 
  {1., 1., 1., 1., 1., 1., 1., 1.}, {1., 1., 1., 1., 1., 1., 1., 1., 1.},  
  {1., 1., 1., 0.99997, 1.00014, 0.999618, 1.00062, 0.9994, 1.00031, 
  0.999931}, {1., 1., 0.999995, 1.00006, 0.999658, 1.00122, 0.997327, 
  1.00367, 0.996932, 1.00143, 0.999717}, {1., 1., 0.999986, 1.00022, 
  0.998241, 1.00831, 0.975462, 1.0466, 0.94311, 1.04312, 0.981529, 
  1.00342}}
_

Mathematicaで試してみた場合は、悪条件の警告メッセージがいくつか表示されます。)

どちらの場合も、単に精度を上げるだけでは解決できません。それは数字の避けられない侵食を遅らせるだけです。

これはあなたが直面するかもしれないものです。解決策は難しいかもしれません。最初に、あなたは製図板に戻るか、ジャーナル/本/何でも調べて、他の誰かがあなたよりも優れた解決策を考え出していないかを見つけます。もう1つは、あきらめるか、問題をより扱いやすいものに再定式化することです。


Dianne O'Learyからの引用を残しておきます。

人生は私たちにいくつかの悪条件の問題を投げかけるかもしれませんが、不安定なアルゴリズムに落ち着く正当な理由はありません。

10
user1372

10進数の10進数は2進数では表現できないため

つまり、1/10は、分母で2の累乗の小数に変換することはできません(これは基本的に浮動小数点数です)。

9
ratchet freak

数学では、有理数は無限にあります。 32ビット変数は2つしか持つことができません32 異なる値、64ビット変数のみ264 値。したがって、正確な表現を持たない無数の有理数があります。

1/3を完全に、または1/100を表すことを可能にするスキームを考え出すことができます。多くの実用的な目的では、これはあまり役に立ちません。大きな例外が1つあります。金融では、小数がポップアップすることがよくあります。これは主に、金融は本質的に人間の活動であり、物理的な活動ではないためです。

そのため、通常は2進浮動小数点を使用し、2進数で表現できない値は丸めます。しかし、金融では、10進浮動小数点を選択し、値を最も近い10進値に丸めます。

9
MSalters