Sıralanan bir sayı dizisinin toplamını hesaplamak için hangi algoritma daha doğrudur?


22

Pozitif sayılar artan bir sonlu dizisi Verilen . Sayıların toplamını hesaplamak için aşağıdaki iki algoritmadan hangisi daha iyidir?z1,z2,.....zn

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

Veya:

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

Bence sayıları en büyükten en küçük sayıya eklemeye başlamak daha iyi olur, çünkü hata küçülür ve küçülür. Ayrıca, çok küçük bir sayıya çok büyük bir sayı eklediğimizde, yaklaşık sonucun büyük sayı olabileceğini de biliyoruz.

Bu doğru mu? Başka ne söylenebilir?

Yanıtlar:


18

İsteğe bağlı kayan nokta sayılarının eklenmesi genellikle bazı yuvarlama hatası verir ve yuvarlama hatası sonucun boyutuyla orantılı olur. Tek bir toplamı hesaplar ve önce en büyük sayıları ekleyerek başlarsanız, ortalama sonuç daha büyük olacaktır. Böylece en küçük sayılarla eklemeye başlarsınız.

Ancak, dört toplam üretirseniz daha iyi sonuç alırsınız (ve daha hızlı çalışır), örneğin: sum1, sum2, sum3, sum4 ile başlayın ve sırasıyla sum1, sum2, sum3, sum4'e dört dizi öğesi ekleyin. Her bir sonuç orijinal toplamın yalnızca 1 / 4'ü civarında olduğu için, hatanız dört kat daha küçüktür.

Hala daha iyi: Sayıları çiftler halinde ekleyin. Sonra sonuçları çiftler halinde ekleyin. Bu sonuçları tekrar çiftler halinde ekleyin ve ekleyeceğiniz iki sayı kalıncaya kadar devam edin.

Çok basit: Daha yüksek hassasiyet kullanın. Çiftlerin toplamını hesaplamak için uzun double kullanın. Yüzenlerin toplamını hesaplamak için çift kullanın.

Mükemmel yakın: Kahan'ın algoritmasına bak, daha önce tarif edilen. En iyisi, en küçük sayıdan başlayarak ekleyerek kullanılır.


26

Bunlar tam sayılar mı yoksa kayan nokta sayıları mı? Kayan nokta olduğunu varsayarsak, ilk seçenek ile giderdim. Birbirine küçük sayıları eklemek, daha sonra büyük sayıları eklemek daha iyidir. İkinci seçenekte, olarak büyük bir sayıya az sayıda ekleyerek bitireceğiz i problemlere yol açabilir artar. İşte kayan nokta aritmetiği hakkında iyi bir kaynak: Her Bilgisayar Bilimcisi Kayan Nokta Aritmetiği Hakkında Bilmeli Gerekenler


24

animal_magic'in cevabı, en küçüğünden en büyüğüne rakamları eklemeniz gerektiği doğrudur, ancak nedenini göstermek için bir örnek vermek istiyorum.

Bize şaşırtıcı bir 3 basamaklı doğruluk veren kayan nokta biçiminde çalıştığımızı varsayalım. Şimdi on numara eklemek istiyoruz:

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

Tabii ki kesin cevap 1009, ama bunu 3 basamaklı biçimde alamıyoruz. 3 haneye yuvarlama, elde ettiğimiz en doğru cevap 1010'dur. En büyüğüne en küçük olanı eklersek, her döngü için:

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

Böylece formatımız için mümkün olan en doğru cevabı alıyoruz. Şimdi en büyüğünden en küçüğüne eklediğimizi varsayalım.

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

Her işlemden sonra kayan nokta sayıları yuvarlandığından, tüm eklemeler yuvarlanır ve hatamız tam olarak 1'den 9'a çıkarılır. Şimdi, eklenecek sayı kümenizin 1000, sonra da yüzlerce veya bir milyona sahip olduğunu hayal edin. Gerçekten doğru olması için, en küçük iki sayıyı toplayacağınızı, ardından sonucu sayı kümenize yerleştirmek istediğinizi unutmayın.


15

Genel durum için, telafi edilmiş toplamı (veya Kahan toplamını) kullanırdım. Numaralar zaten sıralanmadıkça, sıralamak eklemekten çok daha pahalı olacaktır . Kompanzasyonlu toplamlama ayrıca sıralanan toplamdan veya saf toplamdan daha doğrudur (önceki bağlantıya bakınız).

Referanslara gelince, her programcının , temel noktaları birisinin 20 (+/- 10) dakika içinde okuyabileceği ve temellerini anlayabileceği kadar ayrıntılı bir şekilde kapsar. Goldberg'den "Her bilgisayar bilimcisinin kayan nokta aritmetiği hakkında bilmesi gerekenler" klasik referanstır, ancak bildiğim çoğu kişi bu makaleyi kimin tavsiye ettiğini bilmiyorum, çünkü 50 sayfa civarındadır (bundan daha fazlası, bazılarında baskılar) ve yoğun bir şekilde yazılmışlar, bu yüzden bunu insanlar için ilk adım olarak tavsiye etmekte zorlanıyorum. Konuya ikinci kez bakmak için iyidir. Bir ansiklopedik referans, Higham'ın Sayısal Algoritmaların Doğruluğu ve Kararlılığıdır. bu materyali kapsayan kayan nokta aritmetiği hakkında bilmesi gerekenler ve sayısal hataların birçok algoritmada biriktirilmesi; ayrıca 680 sayfa, bu yüzden ilk önce bu referansa bakmam.


2
Tamamlanması için, Higham'ın kitabında sayfa 82'deki orijinal sorunun cevabını bulacaksınız : artan sipariş en iyisidir. Ayrıca, yöntem seçimini tartışan bir Bölüm (4.6) de bulunmaktadır.
Federico Poloni

7

Önceki cevaplar konuyu zaten tartışıyor ve sağlam tavsiyeler veriyor, ancak söylemek istediğim ilave bir tuhaflık var. Çoğu modern mimaride, fortanımladığınız döngü yine de tüm geçici değişkenler sicillere ekleneceğinden, 80-bit genişletilmiş hassasiyetle gerçekleştirilecektir . Yani zaten sayısal hatalardan bir çeşit korunma var. Bununla birlikte, daha karmaşık döngülerde, ara değerler işlemler arasında bellekte depolanacak ve böylece 64 bit olarak kesilecektir. Sanırım bunu

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

özetinizde daha düşük hassasiyet elde etmek için yeterli (!!). Bu nedenle, doğruluğunu kontrol ederken kodunuzu yazdırmak için hata ayıklamak istiyorsanız çok dikkatli olun.

İlgilenenler için, bu makale , hata ayıklama ve analiz işlemlerinin tam olarak bu sorun nedeniyle çok zor olduğu yaygın olarak kullanılan sayısal bir rutinde (Lapack'in sıradaki açıklayıcı QR faktoringi) bir sorunu açıklar.


1
Çoğu modern makine 64-bit'dir ve skaler işlemler için bile SSE veya AVX ünitelerini kullanırlar. Bu birimler 80-bit aritmetiğini desteklemiyor ve işlem argümanları ile aynı dahili hassasiyeti kullanıyor. X87 FPU'nun kullanımı genel olarak önerilmemektedir ve çoğu 64 bit derleyicinin kullanılması için özel seçeneklere ihtiyacı vardır.
Hristo Iliev

1
@HristoIliev Yorumunuz için teşekkürler, bunu bilmiyordum!
Federico Poloni

4

2 seçenek arasından, küçükten büyüğe ekleme, daha az sayısal hata üretecek ve daha büyükten küçüğe ekleyecektir.

Bununla birlikte,> 20 yıl önce benim "Sayısal Yöntemler" dersimde eğitmen bunu belirtti ve akümülatör ile eklenen değerler arasındaki değer arasındaki nispi fark nedeniyle hala gerekenden daha fazla hata getirdiğini söyledi.

Mantıksal olarak, tercih edilen bir çözüm, listedeki en küçük 2 sayıyı eklemektir, ardından toplanan değeri sıralı listeye yeniden eklemektir.

Bunu göstermek için, eklemelerden beri doğal olarak sıralanan toplam değerlerin ikincil bir dizisini oluşturmak için elementler birincil diziden kaldırılırken serbest bırakılan alanı kullanarak bunu verimli bir şekilde (boşlukta ve zamanda) yapabilen bir algoritma geliştirdim. her zaman artan değerlerin toplamıydı. Her yinelemede, en küçük 2 değeri bulmak için her iki dizinin "ipuçları" kontrol edilir.


2

Kullanılacak veri tipini sınırlamadığınızdan, kusursuz bir sonuç elde etmek için, isteğe bağlı uzunluk numaralarını kullanın ... bu durumda sipariş önemli olmaz. Çok daha yavaş olacak, ancak mükemmellik elde etmek zaman alıyor.


0

İkili ağaç eklemeyi kullanın, örneğin, ikili ağacın kökü olarak dağılımın ortalamasını (en yakın sayı) seçin ve grafiğin soluna daha az değer ekleyerek ve sağa vb. . Tek bir ebeveynin tüm alt düğümlerini aşağıdan yukarıya bir yaklaşımla özyinelemeli olarak ekleyin. Bu, toplam hata sayısı ile toplama hatası arttıkça ve ikili ağaç yaklaşımında arttıkça, toplam toplama sayısı 2 bazında log sıralamasıyla arttıkça verimli olacaktır. Bu nedenle, ortalama hata daha az olacaktır.


Bu, orijinal diziye bitişik çiftler eklemekle aynıdır (sıralandığından beri). Tüm değerleri ağaca koymak için hiçbir neden yoktur.
Godric Seer

0

Hristo Iliev yukarıda söylediği gibi, SSU ve AVX komutlarını FPU (AKA NDP) üzerinden tercih etmeyi tercih eden 64 bit derleyiciler için kesinlikle geçerlidir, en azından Microsoft Visual Studio 2013 için. FPU'yu kullanmak, teoride olduğu kadar doğru olarak da daha hızlı. Sizin için önemliyse, son yaklaşımı seçmeden önce önce çeşitli çözümleri test etmenizi öneririm.

Java'da çalışırken, sıklıkla keyfi BigDecimal veri türünü kullanırım. Bu sadece çok kolaydır ve genellikle hızın azaldığını fark etmez. Transandantal fonksiyonların Newton'un metodunu kullanarak sonsuz seri ve sqrt ile hesaplanması bir milisaniye veya daha fazla sürebilir, ancak yapılabilir ve oldukça doğrudur.


0

Bunu sadece burada bıraktım /programming//a/58006104/860099 (oraya gittiğinde, 'kod pasajını göstermek için tıklayın ve düğmeyle çalıştırın)

En büyükten başlayarak toplamın daha büyük hata verdiğini açıkça gösteren JavaScript örneğidir.

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);

Sadece bağlantı cevapları bu sitede tavsiye edilmez. Bağlantıda neler olduğunu açıklayabilir misiniz?
nicoguaro

@nicoguaro Cevabı güncelle - tüm cevaplar çok güzel, ancak işte somut bir örnek
Kamil Kiełczewski
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.