ソートされた数値の配列の合計を計算するのに、どのアルゴリズムがより正確ですか?


22

与えられるのは、正数増加する有限シーケンスです。次の2つのアルゴリズムのうち、数値の合計の計算に適しているのはどれですか?z1z2zn

s=0; 
for \ i=1:n 
    s=s + z_{i} ; 
end

または:

s=0; 
for \ i=1:n 
s=s + z_{n-i+1} ; 
end

私の意見では、エラーはますます小さくなるので、最大の番号から最小の番号に番号を追加し始める方が良いでしょう。また、非常に大きな数値を非常に小さな数値に加算すると、近似結果が大きな数値になる可能性があることもわかっています。

これは正しいです?他に何が言えますか?

回答:


18

通常、任意の浮動小数点数を追加すると丸め誤差が発生し、丸め誤差は結果のサイズに比例します。単一の合計を計算し、最大の数値を最初に追加することから始めた場合、平均結果は大きくなります。したがって、最小の数字から追加を開始します。

ただし、たとえば、sum1、sum2、sum3、sum4から始めて、4つの配列要素をsum1、sum2、sum3、sum4に順番に追加すると、より良い結果が得られます(そして、より速く実行されます)。各結果は平均して元の合計の1/4であるため、エラーは4倍小さくなります。

さらに良いこと:数字をペアで追加します。次に、結果をペアで追加します。これらの結果をペアで再度追加し、追加する2つの数字が残るまで続けます。

非常に簡単:より高い精度を使用します。long doubleを使用して、doubleの合計を計算します。doubleを使用して、フロートの合計を計算します。

完璧に近い:前述のKahanのアルゴリズムを調べます。最小の番号から開始して追加することで、引き続き使用するのが最適です。


26

これらの整数または浮動小数点数ですか?浮動小数点であると仮定して、最初のオプションを選択します。小さい数字を互いに追加し、後で大きい数字を追加することをお勧めします。2番目のオプションを使用すると、iが増加するにつれて大きな数値に小さな数値を追加することになり、問題が発生する可能性があります。浮動小数点演算に関する優れたリソースは次のとおりです。すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと


24

animal_magicの答えは正しいです。最小から最大の数字を追加する必要がありますが、その理由を示す例を挙げたいと思います。

浮動小数点形式で作業していると仮定すると、3桁の驚異的な精度が得られます。次に、10個の数字を追加します。

[1000, 1, 1, 1, 1, 1, 1, 1, 1, 1]

もちろん、正確な答えは1009ですが、3桁の形式では取得できません。3桁に丸めると、得られる最も正確な答えは1010です。最小から最大まで加算すると、各ループで次のようになります。

Loop Index        s
1                 1
2                 2
3                 3
4                 4
5                 5
6                 6
7                 7
8                 8
9                 9
10                1009 -> 1010

したがって、私たちのフォーマットに対して可能な限り正確な答えを得ることができます。ここで、最大から最小に追加すると仮定しましょう。

Loop Index        s
1                 1000
2                 1001 -> 1000
3                 1001 -> 1000
4                 1001 -> 1000
5                 1001 -> 1000
6                 1001 -> 1000
7                 1001 -> 1000
8                 1001 -> 1000
9                 1001 -> 1000
10                1001 -> 1000

浮動小数点数は各操作の後に丸められるため、すべての加算は切り捨てられ、エラーが正確に1から9に増加します。ここで、追加する数字のセットに1000があり、次に100の1の数、または100万の数があると想像してください。本当に正確にするには、最小の2つの数値を合計し、その結果を数値のセットに再分類することに注意してください。


15

一般的なケースでは、補正された加算(またはKahan加算)を使用します。番号が既にソートされていない限り、それらをソートすることはそれらを追加するよりもはるかに高価になります。補正された合計は、ソートされた合計または単純な合計よりも正確です(前のリンクを参照)。

参考文献については、すべてのプログラマーが浮動小数点演算について知っておくべきことは、20(+/- 10)分でそれを読んで基本を理解できるほど十分に詳細な基本ポイントをカバーしています。Goldbergの「すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと」は古典的な参考文献ですが、私が知っているほとんどの人は、論文が50ページ前後(それ以上、印刷)、および密集した散文で書かれているので、私はそれを人々のための最初の行の参照として推奨するのに苦労しています。主題を再確認するのに適しています。百科事典の参照はHighamの精度と数値アルゴリズムの安定性です、この資料、および他の多くのアルゴリズムにおける数値誤差の蓄積について説明しています。680ページもあるので、この参照も最初に見ません。


2
完全を期すために、Highamの本では、82ページの元の質問に対する答えを見つけることができます。増加する順序が最適です。方法の選択について説明するセクション(4.6)もあります。
フェデリコポロニ14

7

これまでの回答では、すでにこの問題について全般的に議論し、適切なアドバイスを提供していますが、追加の癖があります。ほとんどの最新のアーキテクチャでは、for説明したループはすべての一時変数がレジスターに格納されるため、80ビット拡張精度でとにかく実行され、追加の精度が保証されます。そのため、すでに数値エラーから何らかの形で保護されています。ただし、より複雑なループでは、中間値は操作の間にメモリに保存されるため、64ビットに切り捨てられます。私はそう思う

s=0; 
for \ i=1:n 
    printf("Hello World");
    s=s + z_{i} ; 
end

合計の精度を下げるには十分です(!!)。そのため、正確性を確認しながらコードをprintfデバッグする場合は、十分に注意してください。

興味のある方のために、このペーパーでは、この問題のためにデバッグと分析が非常にトリッキーだった、広く使用されている数値ルーチン(Lapackのランクを明らかにするQR分解)の問題について説明します。


1
最新のマシンのほとんどは64ビットであり、スカラー操作でもSSEまたはAVXユニットを使用します。これらのユニットは80ビットの算術演算をサポートせず、操作の引数と同じ内部精度を使用します。現在、x87 FPUの使用は一般的に推奨されておらず、ほとんどの64ビットコンパイラーでは、強制的に使用するために特別なオプションが必要です。
フリストIliev

1
@HristoIlievコメントありがとうございます、私はこれを知りませんでした!
フェデリコポロニ

4

2つのオプションのうち、小さい方から大きい方に追加すると、大きい方から小さい方に追加するよりも数値誤差が少なくなります。

しかし、20年以上前に私の「数値メソッド」クラスでインストラクターがこれを述べ、アキュムレータと追加された値の相対的な値の違いのために、これが必要以上のエラーを導入していることに気付きました。

論理的には、リストに最小の2つの数値を追加し、合計値を並べ替えられたリストに再挿入するのが望ましい解決策です。

それを実証するために、要素がプライマリ配列から削除されたときに解放されたスペースを使用して、追加以来本質的に順序付けられた合計値のセカンダリ配列を構築することにより、効率的に(空間と時間で)それを行うことができるアルゴリズムを作成しました常に増加する値の合計でした。各反復で、2つの最小値を見つけるために、両方の配列の「ヒント」がチェックされます。


2

使用するデータ型を制限しなかったため、完全に正確な結果を得るには、任意の長さの数字を使用するだけです。その場合、順序は関係ありません。かなり遅くなりますが、完璧を得るには時間がかかります。


0

バイナリツリー加算を使用します。つまり、バイナリツリーのルートとして分布の平均(最も近い数)を選択し、グラフの左側に小さい値を、右側に大きい値を追加することでソートされたバイナリツリーを作成します。 。単一の親のすべての子ノードをボトムアップ方式で再帰的に追加します。これは、加算の数とともに平均誤差が増加するため効率的であり、バイナリツリーアプローチでは、加算の数は2を底とするlog nのオーダーになります。したがって、平均誤差は小さくなります。


これは、元の配列に隣接するペアを追加するのと同じです(ソートされるため)。すべての値をツリーに入れる理由はありません。
ゴドリックシーア14

0

Hristo IlievがFPU(別名NDP)よりもSSEおよびAVX命令を好む64ビットコンパイラについて上記で述べたことは、少なくともMicrosoft Visual Studio 2013では絶対に当てはまります。しかし、私が使用していた倍精度浮動小数点演算ではFPUを使用することは、理論的にはより正確であるだけでなく、実際にはより高速です。あなたにとって重要な場合は、最終的なアプローチを選択する前に、さまざまなソリューションを最初にテストすることをお勧めします。

Javaで作業するとき、私は非常に頻繁に任意精度のBigDecimalデータ型を使用します。簡単すぎて、通常は速度の低下に気づきません。ニュートンの方法を使用して、無限級数とsqrtで超越関数を計算するには1ミリ秒以上かかる場合がありますが、実行可能で非常に正確です。


0

/programming//a/58006104/860099(ここに行ったら、「コードスニペットを表示」をクリックしてボタンで実行するだけです)

最大から始まる合計がより大きなエラーを与えることを明確に示すJavaScriptの例です

arr=[9,.6,.1,.1,.1,.1];

sum     =             arr.reduce((a,c)=>a+c,0);  // =  9.999999999999998
sortSum = [...arr].sort().reduce((a,c)=>a+c,0);  // = 10

console.log('sum:     ',sum);
console.log('sortSum:',sortSum);

このサイトではリンクのみの回答はお勧めしません。リンクで提供されているものを説明できますか?
ニコグアロ

@nicoguaro私は回答を更新します-すべての回答は非常に素晴らしいですが、ここに具体的な例があります
カミル・キーレフスキー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.