web-dev-qa-db-ja.com

浮動小数点丸め誤差の解決策

多くの数学的計算を扱うアプリケーションを作成する際に、特定の数値が丸め誤差を引き起こすという問題に遭遇しました。

浮動小数点は正確ではない であることは理解していますが、問題はhowです。正確な数値を処理して、計算はそれらに基づいて実行されます。浮動小数点の丸めは問題を引き起こしませんか?

18
JNL

浮動小数点の丸めを行わない代替数値型を作成するには、3つの基本的なアプローチがあります。これらの共通のテーマは、整数演算をさまざまな方法で使用することです。

理由

分子と分母を使用して、数値を整数部分と有理数として表します。番号15.589は、w: 15; n: 589; d:1000として表されます。

0.25(w: 0; n: 1; d: 4)に追加すると、LCMが計算され、2つの数値が加算されます。これは多くの状況でうまく機能しますが、互いに素である多くの有理数を扱う場合、非常に大きな数になる可能性があります。

固定点

全体と小数部分があります。すべての数値はその精度に丸められます(そのWordはありますが、どこにあるか知っています)。たとえば、小数点が3つある固定小数点を使用できます。 15.589 + 0.250は、小数部に589 + 250 % 1000を追加します(その後、部分全体への桁上げ)。これは、既存のデータベースで非常にうまく機能します。前述のように、丸めはありますが、それがどこにあるかはわかっていて、必要以上に正確になるように指定できます(小数点以下3桁まで測定しているため、4に固定します)。

浮動小数点

値と精度を保存します。 15.589は、値の場合は15589、精度の場合は3として格納され、0.2525および2として格納されます。これは任意の精度を処理できます。私believeこれは、JavaのBigDecimalの内部が使用している(最近見たことがありません)ものです。ある時点で、それをこのフォーマットから戻して表示する必要があります-これには丸めが含まれる可能性があります(ここでも、どこにあるかを制御します)。


表現の選択を決定したら、これを使用する既存のサードパーティライブラリを見つけるか、独自のライブラリを作成できます。独自に作成する場合は、必ずユニットテストを実施し、数学が正しく行われていることを確認してください。

22
user40980

浮動小数点値に丸めの問題があり、丸めの問題に遭遇したくない場合、論理的には、浮動小数点値を使用しないことが唯一の行動方針であるということになります。

ここでの質問は、「浮動小数点変数なしの非整数値を含む計算をどのように行うのですか?」答えは 任意精度データ型 です。ハードウェアではなくソフトウェアで実装する必要があるため、計算は遅くなりますが、正確です。使用している言語を言わなかったため、パッケージをお勧めすることはできませんが、ほとんどの一般的なプログラミング言語で使用できる任意精度ライブラリがあります。

10
Mason Wheeler

浮動小数点演算は通常非常に正確で(doubleの場合は15桁の10進数)、非常に柔軟です。精度の桁数を大幅に削減する計算を行っているときに問題が発生します。ここではいくつかの例を示します。

  • 減算時のキャンセル:_1234567890.12345 - 1234567890.12300_、結果_0.0045_の精度は、小数点以下2桁のみです。これは、同様の大きさの2つの数値を引くと必ず発生します。

  • 精度の飲み込み:_1234567890.12345 + 0.123456789012345_は_1234567890.24691_に評価され、2番目のオペランドの最後の10桁は失われます。

  • 乗算:2つの15桁の数値を乗算すると、結果には30桁が格納される必要があります。しかし、それらを保存することはできないため、最後の15ビットは失われます。これは、sqrt()と組み合わせると特に厄介です(sqrt(x*x + y*y)のように:結果の精度は7.5桁しかありません。

これらは、注意する必要がある主な落とし穴です。そして、それらに気づいたら、それらを回避する方法で数学を定式化しようとすることができます。たとえば、ループ内で何度も値をインクリメントする必要がある場合は、次のようにしないでください。

_for(double f = f0; f < f1; f += df) {
_

数回の反復の後、より大きなfdfの精度の一部を飲み込みます。さらに悪いことに、エラーが追加され、dfを小さくすると全体的な結果が悪化するという、矛盾した状況が発生します。これを書いてください:

_for(int i = 0; i < (f1 - f0)/df; i++) {
    double f = f0 + i*df;
_

増分を1つの乗算で組み合わせるため、結果のfは15桁の10進数の精度になります。

これは単なる例であり、他の理由による精度の低下を回避する他の方法があります。しかし、関連する値の大きさについて考えたり、ペンと紙で数学をしたり、すべてのステップの後に固定桁数に丸めたりするとどうなるかを想像することは、すでに多くの助けになります。

問題がないことを確認する方法:浮動小数点演算の問題について学ぶか、そうした人を雇うか、常識を働かせます。

最初の問題は精度です。多くの言語では "float"と "double"( "double double precision"の略)があり、多くの場合、 "float"は約7桁の精度を提供し、doubleは15を提供します。一般的な意味は、精度が問題になる可能性がある状況では、15桁は7桁よりもはるかに優れています。少し問題の多い多くの状況では、「double」を使用するとそれを回避できることを意味し、「float」を使用すると回避できることを意味します。会社の時価総額が7,000億ドルであるとしましょう。これを浮動小数点数で表し、最下位ビットは$ 65536です。それを倍精度で表現し、最下位ビットは約0.012セントです。ですから、本当に、本当に何をしているかを理解していない限り、floatではなくdoubleを使用します。

2番目の問題は、原則の問題です。同じ結果が得られるはずの2つの異なる計算を行う場合、丸め誤差のために計算が行われないことがよくあります。等しいはずの2つの結果は「ほぼ等しい」になります。 2つの結果が接近している場合、実際の値は等しい可能性があります。またはそうではないかもしれません。それを覚えておく必要があり、「xは絶対にyより大きい」または「xは絶対にyより小さい」または「xとyは等しいかもしれない」という関数を作成して使用する必要があります。

たとえば、「xを最も近い整数に切り捨てる」など、丸めを使用すると、この問題はさらに悪化します。 120 * 0.05を掛けると、結果は6になりますが、「6に非常に近い数値」が得られます。次に、「最も近い整数に切り捨てる」場合、その「6に非常に近い数値」は「6未満」であり、5に丸められる可能性があります。また、精度がどれほど重要でもないことに注意してください。関係ありませんどのくらい近いか 6まで。結果が6未満である限り。

そして第三に、いくつかの問題は難しいです。つまり、迅速で簡単なルールはありません。コンパイラーがより精度の高い「long double」をサポートしている場合は、「long double」を使用して、違いがあるかどうかを確認できます。それが違いを生まない場合、あなたは大丈夫であるか、あなたは本当にトリッキーな問題を抱えています。それがあなたが期待する種類の違いを作るならば(十二進十二進の変化のように)、あなたはおそらく大丈夫です。それがあなたの結果を本当に変えるならば、あなたは問題を抱えています。助けを求める。

2
gnasher729

ほとんどの人は、実際に問題を別の場所に移動したばかりで、BigDecimalが2倍に叫ぶのを見て間違いを犯します。 Doubleは符号ビットを与えます:1ビット、指数幅:11ビット。有効桁数:53ビット(52は明示的に格納)。 doubleの性質により、整数全体が大きくなると、相対的な精度が失われます。ここで使用する相対精度を計算する方法は次のとおりです。

計算でのdoubleの相対精度は、次の式を使用します2 ^ E <= abs(X)<2 ^(E + 1)

epsilon = 2 ^(E-10)%16ビットfloatの場合(半精度)

 Accuracy Power | Accuracy -/+| Maximum Power | Max Interger Value
 2^-1           | 0.5         | 2^51          | 2.2518E+15
 2^-5           | 0.03125     | 2^47          | 1.40737E+14
 2^-10          | 0.000976563 | 2^42          | 4.39805E+12
 2^-15          | 3.05176E-05 | 2^37          | 1.37439E+11
 2^-20          | 9.53674E-07 | 2^32          | 4294967296
 2^-25          | 2.98023E-08 | 2^27          | 134217728
 2^-30          | 9.31323E-10 | 2^22          | 4194304
 2^-35          | 2.91038E-11 | 2^17          | 131072
 2^-40          | 9.09495E-13 | 2^12          | 4096
 2^-45          | 2.84217E-14 | 2^7           | 128
 2^-50          | 8.88178E-16 | 2^2           | 4

つまり、精度を+/- 0.5(または2 ^ -1)にする場合、数値の最大サイズは2 ^ 52です。これより大きく、浮動小数点数間の距離が0.5より大きい。

+/- 0.0005(約2 ^ -11)の精度が必要な場合、数値の最大サイズは2 ^ 42です。これより大きく、浮動小数点数間の距離が0.0005より大きい。

これ以上良い答えは出せません。ユーザーは、必要な計算を実行するときに必要な精度とその単位値(メートル、フィート、インチ、mm、cm)を把握する必要があります。ほとんどの場合、シミュレートする世界の規模に応じて、フロートは単純なシミュレーションで十分です。

それは言いたいことですが、100メートルx 100メートルの世界をシミュレートすることだけを目的としている場合は、2 ^ -45に近い精度のどこかになるでしょう。これは、cpu内の最新のFPUがネイティブタイプサイズの外で計算を行う方法には及んでおらず、計算が完了した後でのみ(FPU丸めモードに応じて)ネイティブタイプサイズに丸められます。

0
Chad