合計の順序を変更すると、異なる結果が返されるのはなぜですか?
23.53 + 5.88 + 17.64
= 47.05
23.53 + 17.64 + 5.88
= 47.050000000000004
JavaとJavaScriptはどちらも同じ結果を返します。
浮動小数点数が2進数で表現される方法が原因で、一部の有理数(1/3-0.333333 ...など)を正確に表現できないことを理解しています。
要素の順序を変更するだけで結果が影響を受けるのはなぜですか?
合計の順序を変更すると、異なる結果が返されるのはなぜですか?
23.53 + 5.88 + 17.64
= 47.05
23.53 + 17.64 + 5.88
= 47.050000000000004
JavaとJavaScriptはどちらも同じ結果を返します。
浮動小数点数が2進数で表現される方法が原因で、一部の有理数(1/3-0.333333 ...など)を正確に表現できないことを理解しています。
要素の順序を変更するだけで結果が影響を受けるのはなぜですか?
回答:
この質問は愚かかもしれませんが、要素の順序を変更するだけで結果が影響を受けるのはなぜですか?
値の大きさに基づいて、値が丸められるポイントを変更します。この種の例として、2進浮動小数点の代わりに、有効桁数が4桁の10進浮動小数点型を使用していたとします。各加算は「無限」の精度で実行され、次に丸められます。最も近い表現可能な数。2つの合計は次のとおりです。
1/3 + 2/3 + 2/3 = (0.3333 + 0.6667) + 0.6667
= 1.000 + 0.6667 (no rounding needed!)
= 1.667 (where 1.6667 is rounded to 1.667)
2/3 + 2/3 + 1/3 = (0.6667 + 0.6667) + 0.3333
= 1.333 + 0.3333 (where 1.3334 is rounded to 1.333)
= 1.666 (where 1.6663 is rounded to 1.666)
これが問題になるために、整数以外の値も必要ありません。
10000 + 1 - 10000 = (10000 + 1) - 10000
= 10000 - 10000 (where 10001 is rounded to 10000)
= 0
10000 - 10000 + 1 = (10000 - 10000) + 1
= 0 + 1
= 1
これにより、重要な部分は有効桁数が制限されていることであり、小数点以下の桁数が制限されていないことが重要であることがより明確に示されます。小数点以下の桁数を常に同じに保つことができれば、少なくとも加算と減算があれば、問題はありません(値がオーバーフローしない限り)。問題は、より大きな数値に到達すると、より小さな情報が失われることです。この場合、10001は10000に丸められます。(これは、Eric Lippertが彼の回答で指摘した問題の例です。)
右側の最初の行の値はすべてのケースで同じであることに注意することが重要です。したがって、10進数(23.53、5.88、17.64)はdouble
値として正確に表されないことを理解することが重要ですが、上記の問題のため、問題のみ。
May extend this later - out of time right now!
それ@ジョンのために熱心に待っている
double
andと同じfloat
です。 1つ以上離れています。
これがバイナリで何が起こっているかです。ご存知のように、一部の浮動小数点値は、たとえ10進数で正確に表すことができたとしても、2進数で正確に表すことができません。これらの3つの数値は、その事実の単なる例です。
このプログラムを使用して、各数値の16進表現と各加算の結果を出力しました。
public class Main{
public static void main(String args[]) {
double x = 23.53; // Inexact representation
double y = 5.88; // Inexact representation
double z = 17.64; // Inexact representation
double s = 47.05; // What math tells us the sum should be; still inexact
printValueAndInHex(x);
printValueAndInHex(y);
printValueAndInHex(z);
printValueAndInHex(s);
System.out.println("--------");
double t1 = x + y;
printValueAndInHex(t1);
t1 = t1 + z;
printValueAndInHex(t1);
System.out.println("--------");
double t2 = x + z;
printValueAndInHex(t2);
t2 = t2 + y;
printValueAndInHex(t2);
}
private static void printValueAndInHex(double d)
{
System.out.println(Long.toHexString(Double.doubleToLongBits(d)) + ": " + d);
}
}
このprintValueAndInHex
メソッドは単なる16進プリンターヘルパーです。
出力は次のとおりです。
403787ae147ae148: 23.53
4017851eb851eb85: 5.88
4031a3d70a3d70a4: 17.64
4047866666666666: 47.05
--------
403d68f5c28f5c29: 29.41
4047866666666666: 47.05
--------
404495c28f5c28f6: 41.17
4047866666666667: 47.050000000000004
最初の4つの数字でありx
、y
、z
、およびs
の進表現。IEEE浮動小数点表現では、ビット2〜12は2進指数、つまり数値のスケールを表します。(最初のビットは符号ビットで、残りのビットは仮数です。)表される指数は、実際には2進数から1023を引いたものです。
最初の4つの数値の指数が抽出されます。
sign|exponent
403 => 0|100 0000 0011| => 1027 - 1023 = 4
401 => 0|100 0000 0001| => 1025 - 1023 = 2
403 => 0|100 0000 0011| => 1027 - 1023 = 4
404 => 0|100 0000 0100| => 1028 - 1023 = 5
追加の最初のセット
2番目の数値(y
)は、より小さな値です。getにこれら2つの数値を加算するx + y
と、2番目の数値(01
)の最後の2ビットが範囲外にシフトされ、計算に含まれません。
第二添加を追加x + y
してz
、同じ規模の2つの数値を追加します。
追加の2番目のセット
ここでx + z
は、最初に発生します。それらは同じスケールですが、スケールがより高い数値を生成します。
404 => 0|100 0000 0100| => 1028 - 1023 = 5
2番目の加算によりx + z
とが加算されy
、3ビットがから削除さy
れて数値が加算されます(101
)。4047866666666666
最初の加算4047866666666667
セットと2番目の加算セットでは結果が次の浮動小数点数になるため、ここでは上向きの丸めが必要です。そのエラーは、合計のプリントアウトに表示するのに十分なほど重大です。
結論として、IEEE番号に対して数学演算を実行するときは注意してください。一部の表現は不正確であり、縮尺が異なるとさらに不正確になります。可能であれば、同様のスケールの数を加算および減算します。
=)
16進プリンターヘルパーに+1するのが好きです... それは本当にすてきです!
ジョンの答えはもちろん正しい。あなたの場合、エラーは、単純な浮動小数点演算を実行するときに累積するエラーよりも大きくはありません。あるケースではエラーが発生せず、別のケースでは小さなエラーが発生するシナリオがあります。それは実際にはそれほど興味深いシナリオではありません。良い質問は次のとおりです。計算の順序を変更すると、小さなエラーから(比較的)大きなエラーになるシナリオはありますか?答えは明確にイエスです。
例を考えてみましょう:
x1 = (a - b) + (c - d) + (e - f) + (g - h);
対
x2 = (a + c + e + g) - (b + d + f + h);
対
x3 = a - b + c - d + e - f + g - h;
明らかに正確な算術ではそれらは同じです。x1とx2およびx3の値が大きく異なるように、a、b、c、d、e、f、g、hの値を見つけようとするのは楽しいことです。できるかどうか確認してください!
double d = double.MaxValue; Console.WriteLine(d + d - d - d); Console.WriteLine(d - d + d - d);
-出力が0と無限である
これは実際には、JavaとJavaScriptだけではなく、floatやdoubleを使用するプログラミング言語に影響を与える可能性があります。
メモリでは、浮動小数点はIEEE 754の規定に沿った特別な形式を使用します(コンバーターは私よりもはるかに優れた説明を提供します)。
とにかく、これがfloatコンバータです。
http://www.h-schmidt.net/FloatConverter/
操作の順序に関することは、操作の「細かさ」です。
最初の行では、最初の2つの値から29.41が得られ、指数として2 ^ 4が得られます。
2行目は41.17となり、指数として2 ^ 5が得られます。
指数を大きくすることで重要な数字を失い、結果が変わる可能性があります。
右端の最後のビットを41.17オンとオフにしてみてください。指数の1/2 ^ 23と同じくらい「重要ではない」ものが、この浮動小数点の違いを引き起こすのに十分であることがわかります。
編集:重要な数字を覚えている人にとって、これはそのカテゴリに分類されます。10 ^ 4 + 4999の有効数字が1の場合、10 ^ 4になります。この場合、有意な数値ははるかに小さくなりますが、.00000000004を付加した結果を確認できます。
私はそれが評価の順番に関係していると思います。合計は自然に数学の世界では同じですが、バイナリの世界ではA + B + C = Dではなく、
A + B = E
E + C = D(1)
したがって、浮動小数点数が降りることのできる二次ステップがあります。
順番を変えると
A + C = F
F + B = D(2)
(2.0^53 + 1) - 1 == 2.0^53 - 1 != 2^53 == 2^53 + (1 - 1)
です(例:)。したがって、はい。合計の順序やその他の演算を選択するときは注意してください。一部の言語には「高精度」の合計を実行する組み込みmath.fsum
関数が用意されているため(たとえばpythonなど)、単純な合計アルゴリズムの代わりにこれらの関数の使用を検討する場合があります。