多くの数学的計算を扱うアプリケーションを構築する際に、特定の数値が丸め誤差を引き起こすという問題に遭遇しました。
私は浮動小数点が正確ではないことを理解していますが、問題は正確な数値をどのように処理して浮動小数点の丸めが問題を引き起こさないようにするのですか?
distanceTraveled(startVel, duration, acceleration)
をテストします。
多くの数学的計算を扱うアプリケーションを構築する際に、特定の数値が丸め誤差を引き起こすという問題に遭遇しました。
私は浮動小数点が正確ではないことを理解していますが、問題は正確な数値をどのように処理して浮動小数点の丸めが問題を引き起こさないようにするのですか?
distanceTraveled(startVel, duration, acceleration)
をテストします。
回答:
浮動小数点の丸めのない代替数値タイプを作成するには、3つの基本的なアプローチがあります。これらの共通のテーマは、代わりにさまざまな方法で整数演算を使用することです。
理性
分子と分母で整数と有理数として数値を表します。番号15.589
はとして表されw: 15; n: 589; d:1000
ます。
0.25(つまりw: 0; n: 1; d: 4
)に追加すると、LCMを計算してから2つの数値を追加します。これは多くの状況でうまく機能しますが、互いに素な多数の有理数を使用している場合、非常に大きな数になる可能性があります。
不動点
全体と小数部分があります。すべての数値はその精度に丸められます(その単語はありますが、どこにあるかは知っています)。たとえば、小数点が3つある固定小数点を使用できます。 15.589
+ 0.250
は589 + 250 % 1000
、小数部分の加算になります(そして、すべての桁上げが行われます)。これは、既存のデータベースで非常にうまく機能します。前述のように、丸めがありますが、それがどこにあるか知っていて、必要以上に正確になるように指定できます(小数点以下3桁まで測定しているので、4に固定します)。
浮動小数点
値と精度を保存します。 15.589
は15589
値と3
精度0.25
として保存されますが25
、とは保存され2
ます。これにより、任意の精度を処理できます。これは、JavaのBigDecimalの内部(最近見ていない)が使用しているものだと思います。ある時点で、この形式から取り出して表示する必要があります。これには、丸めが含まれる場合があります(ここでも、場所を制御します)。
表現の選択を決定したら、これを使用する既存のサードパーティライブラリを見つけるか、独自のライブラリを作成できます。独自のコードを作成するときは、必ず単体テストを行い、正しく計算していることを確認してください。
浮動小数点値に丸めの問題があり、丸めの問題に遭遇する必要がない場合、論理的には唯一の措置は浮動小数点値を使用しないことです。
ここで、「浮動小数点変数を使用せずに整数以外の値を含む数学を実行するにはどうすればよいですか」という質問になります。答えは任意精度データ型です。ハードウェアではなくソフトウェアで実装する必要があるため、計算は遅くなりますが、正確です。使用している言語を言わなかったので、パッケージを推奨することはできませんが、ほとんどの一般的なプログラミング言語で利用できる任意の精度のライブラリがあります。
lot of mathematical calculations
では役に立ちませんし、答えもありません。ほとんどの場合(通貨を扱っていない場合)、floatで十分です。
浮動小数点演算は、通常、非常に正確(10進数で15桁double
)で非常に柔軟です。精度の桁数を大幅に削減する数学を実行しているときに問題が発生します。ここではいくつかの例を示します。
減算のキャンセル: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) {
数回の反復の後、大きい方f
はの精度の一部を飲み込みdf
ます。さらに悪いことに、エラーがdf
合計されると、小さいほど全体的な結果が悪化するという直感に反する状況になります。より良いこれを書いてください:
for(int i = 0; i < (f1 - f0)/df; i++) {
double f = f0 + i*df;
増分を1回の乗算で組み合わせるため、結果f
は15桁の10進数まで正確になります。
これは単なる例であり、他の理由による精度の低下を回避する方法は他にもあります。しかし、関与する値の大きさを考え、ペンと紙で計算を行い、各ステップの後に固定桁数に丸めた場合に何が起こるかを想像することは、すでに非常に役立ちます。
問題がないことを確認する方法:浮動小数点演算の問題について学習するか、問題のある人を雇うか、常識を使用します。
最初の問題は精度です。多くの言語では、「float」と「double」(「double precision」を表すdouble)があり、多くの場合、「float」は約7桁の精度を提供しますが、doubleは15を提供します。精度が問題になる可能性のある状況では、15桁は7桁よりもはるかに優れています。わずかに問題の多い多くの状況で、「double」を使用することはそれを回避することを意味し、「float」はそうしないことを意味します。会社の時価総額が7,000億ドルだとしましょう。これをフロートで表し、最下位ビットは$ 65536です。doubleを使用して表します。最下位ビットは約0.012セントです。ですから、あなたが本当に何をしているのか本当にわかっていない限り、フロートではなくダブルを使用します。
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」を使用して、違いが生じるかどうかを確認できます。違いがない場合は、OKであるか、本当に難しい問題があります。それがあなたが期待する種類の違いを作るなら(12番目の小数での変化のように)、あなたはおそらく大丈夫です。結果が本当に変わる場合は、問題があります。助けを求める。
ほとんどの人は、実際に問題を別の場所に移動しただけで、BigDecimalの叫び声が2倍になったときに間違いを犯します。Doubleは符号ビットを提供します:1ビット、指数幅:11ビット。有効桁数:53ビット(52が明示的に保存されます)。doubleの性質により、interger全体が大きくなると、相対的な精度が失われます。ここで使用する相対精度を計算するために、以下があります。
計算におけるdoubleの相対精度は、次のfoluma 2 ^ E <= abs(X)<2 ^(E + 1)を使用します
epsilon = 2 ^(E-10)%16ビット浮動小数点の場合(半精度)
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)を把握する必要があります。ほとんどの場合、シミュレートしたい世界の規模に応じて、単純なシミュレーションではfloatで十分です。
言うまでもないことですが、100メートルx 100メートルの世界をシミュレートすることだけを目的としている場合は、2 ^ -45に近い精度のどこかになります。これは、cpu内の最新のFPUがネイティブタイプサイズ以外の計算を行う方法には入らず、計算が完了すると(FPU丸めモードに応じて)ネイティブタイプサイズに丸められます。