Algoritma mana yang lebih akurat untuk menghitung jumlah array angka yang diurutkan?


22

Diberikan adalah peningkatan urutan angka positif terbatas . Manakah dari dua algoritma berikut ini yang lebih baik untuk menghitung jumlah angka?z1,z2,.....zn

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

Atau:

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

Menurut pendapat saya akan lebih baik untuk mulai menambahkan angka dari yang terbesar ke yang terkecil, karena kesalahan semakin kecil. Kita juga tahu bahwa ketika kita menambahkan angka yang sangat besar ke angka yang sangat kecil, hasil perkiraan bisa berupa angka besar.

Apakah ini benar? apalagi yang bisa dikatakan?

Jawaban:


18

Menambahkan angka floating point yang sewenang-wenang biasanya akan memberikan kesalahan pembulatan, dan kesalahan pembulatan akan sebanding dengan ukuran hasilnya. Jika Anda menghitung jumlah tunggal dan mulai dengan menambahkan angka terbesar terlebih dahulu, hasil rata-rata akan lebih besar. Jadi, Anda akan mulai menambahkan dengan angka terkecil.

Tetapi Anda mendapatkan hasil yang lebih baik (dan itu berjalan lebih cepat) jika Anda menghasilkan empat jumlah, misalnya: Mulai dengan sum1, sum2, sum3, sum4 dan tambahkan empat elemen array pada gilirannya ke sum1, sum2, sum3, sum4. Karena setiap hasil rata-rata hanya 1/4 dari jumlah awal, kesalahan Anda empat kali lebih kecil.

Lebih baik lagi: Tambahkan angka berpasangan. Kemudian tambahkan hasilnya berpasangan. Tambahkan hasil itu berpasangan lagi, dan seterusnya hingga Anda memiliki dua angka untuk ditambahkan.

Sangat sederhana: Gunakan presisi yang lebih tinggi. Gunakan panjang ganda untuk menghitung jumlah ganda. Gunakan dobel untuk menghitung jumlah pelampung.

Close to perfect: Cari algoritma Kahan, yang dijelaskan sebelumnya. Terbaik masih digunakan dengan menambahkan dimulai dengan angka terkecil.


26

Apakah bilangan bulat atau angka floating point ini? Dengan asumsi titik apung, saya akan pergi dengan opsi pertama. Lebih baik menambahkan angka yang lebih kecil satu sama lain, kemudian menambahkan angka yang lebih besar nanti. Dengan opsi kedua, Anda akhirnya akan menambahkan angka kecil ke angka besar saat saya bertambah, yang dapat menyebabkan masalah. Berikut adalah sumber yang bagus tentang aritmatika floating point: Apa yang Harus Diketahui Setiap Ilmuwan Komputer Tentang Aritmatika Floating-Point


24

Jawaban animal_magic benar bahwa Anda harus menambahkan angka dari terkecil ke terbesar, namun saya ingin memberikan contoh untuk menunjukkan alasannya.

Asumsikan kita sedang bekerja dalam format floating point yang memberi kita akurasi 3 digit. Sekarang kami ingin menambahkan sepuluh angka:

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

Tentu saja jawaban pastinya adalah 1009, tetapi kami tidak dapat memperolehnya dalam format 3 digit. Membulatkan menjadi 3 digit, jawaban paling akurat yang kami dapatkan adalah 1010. Jika kami menambahkan terkecil ke terbesar, pada setiap loop yang kami dapatkan:

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

Jadi kami mendapatkan jawaban yang paling akurat untuk format kami. Sekarang mari kita asumsikan bahwa kita menambahkan dari yang terbesar ke yang terkecil.

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

Karena angka floating point dibulatkan setelah setiap operasi, semua penambahan dibulatkan, meningkatkan kesalahan kami dari 1 menjadi 9 dari tepat. Sekarang bayangkan jika set angka yang Anda tambahkan memiliki 1000, dan kemudian seratus 1, atau satu juta. Perhatikan bahwa untuk menjadi benar-benar akurat, Anda ingin menjumlahkan dua angka terkecil, kemudian memasukkan hasilnya ke dalam set angka Anda.


15

Untuk kasus umum, saya akan menggunakan penjumlahan terkompensasi (atau penjumlahan Kahan). Kecuali jika jumlahnya sudah diurutkan, mengurutkannya akan jauh lebih mahal daripada menambahkannya . Penjumlahan terkompensasi juga lebih akurat daripada penjumlahan yang disortir atau penjumlahan naif (lihat tautan sebelumnya).

Adapun referensi, Apa yang harus diketahui setiap programmer tentang floating-point aritmatika mencakup poin-poin dasar secara cukup rinci sehingga seseorang dapat membacanya dalam 20 (+/- 10) menit dan memahami dasar-dasarnya. "Apa yang harus diketahui oleh setiap ilmuwan komputer tentang aritmatika titik-mengambang" oleh Goldberg adalah referensi klasik, tetapi kebanyakan orang yang saya kenal yang merekomendasikan makalah itu belum membacanya secara terperinci, karena sekitar 50 halaman (lebih dari itu, dalam beberapa cetakan), dan ditulis dalam prosa padat, jadi saya kesulitan merekomendasikannya sebagai referensi lini pertama untuk orang. Baik untuk melihat subjek yang kedua. Referensi ensiklopedis adalah Akurasi dan Stabilitas Algoritma Numerik Higham, yang mencakup materi ini, serta akumulasi kesalahan numerik dalam banyak algoritma lainnya; itu juga 680 halaman, jadi saya juga tidak akan melihat referensi ini.


2
Untuk kelengkapan, dalam buku Higham, Anda akan menemukan jawaban untuk pertanyaan asli di halaman 82 : peningkatan pemesanan adalah yang terbaik. Ada juga Bagian (4.6) yang membahas pilihan metode.
Federico Poloni

7

Jawaban sebelumnya sudah membahas masalah ini secara luas dan memberikan nasihat, tetapi ada kekhasan tambahan yang ingin saya sebutkan. Pada sebagian besar arsitektur modern, forloop yang telah Anda uraikan akan tetap dilakukan dalam presisi diperluas 80-bit , yang menjamin akurasi tambahan, karena semua variabel sementara akan dimasukkan ke dalam register. Jadi, Anda sudah memiliki beberapa bentuk perlindungan dari kesalahan numerik. Namun, dalam loop yang lebih rumit, nilai-nilai perantara akan disimpan dalam memori di antara operasi, dan karenanya terpotong menjadi 64 bit. Saya rasa itu

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

cukup untuk mendapatkan presisi yang lebih rendah dalam penjumlahan Anda (!!). Jadi berhati-hatilah jika Anda ingin printf-debug kode Anda sambil memeriksa keakuratan.

Bagi yang berminat, makalah ini menjelaskan masalah dalam rutinitas numerik yang banyak digunakan (faktorisasi QR peringkat-peringkat Lapack) yang debugging dan analisisnya sangat rumit justru karena masalah ini.


1
Sebagian besar mesin modern 64-bit dan mereka menggunakan unit SSE atau AVX bahkan untuk operasi skalar. Unit-unit itu tidak mendukung aritmatika 80-bit dan menggunakan presisi internal yang sama dengan argumen operasi. Penggunaan FPU x87 umumnya tidak disarankan sekarang dan sebagian besar kompiler 64-bit membutuhkan opsi khusus untuk dipaksa menggunakannya.
Hristo Iliev

1
@HristoIliev Terima kasih atas komentarnya, saya tidak tahu ini!
Federico Poloni

4

Dari 2 opsi, menambahkan dari yang lebih kecil ke yang lebih besar akan menghasilkan lebih sedikit kesalahan numerik kemudian menambahkan dari yang lebih besar ke yang lebih kecil.

Namun,> 20 tahun yang lalu di kelas "Metode Numerik" saya, instruktur menyatakan ini dan terpikir oleh saya bahwa ini masih menimbulkan lebih banyak kesalahan daripada yang diperlukan karena perbedaan relatif dalam nilai antara akumulator dan nilai yang ditambahkan.

Secara logis, solusi yang lebih disukai adalah dengan menambahkan 2 angka terkecil dalam daftar, lalu memasukkan kembali nilai yang dijumlahkan ke dalam daftar yang diurutkan.

Untuk mendemonstrasikannya, saya membuat algoritma yang bisa melakukan itu secara efisien (dalam ruang dan waktu) dengan menggunakan ruang yang dibebaskan saat elemen dihapus dari array primer untuk membangun array sekunder dari nilai yang dijumlahkan yang secara inheren dipesan sejak penambahan adalah jumlah nilai yang selalu meningkat. Pada setiap iterasi, "tips" dari kedua array kemudian diperiksa untuk menemukan 2 nilai terkecil.


2

Karena Anda tidak membatasi tipe data yang akan digunakan, untuk mencapai hasil yang sangat akurat, cukup gunakan angka panjang arbitrer ... dalam hal ini urutannya tidak akan menjadi masalah. Ini akan jauh lebih lambat, tetapi mendapatkan kesempurnaan memang membutuhkan waktu.


0

Gunakan penambahan pohon biner, yaitu, Pilih rata-rata distribusi (nomor terdekat) sebagai akar dari pohon biner, dan buat pohon biner yang disortir dengan menambahkan nilai yang lebih rendah di sebelah kiri grafik dan yang lebih besar di sebelah kanan grafik dan seterusnya . Menambahkan semua simpul anak dari satu orangtua secara rekursif dalam pendekatan bottom-up. Ini akan efisien karena kesalahan rata-rata meningkat dengan jumlah penjumlahan dan dalam pendekatan pohon biner, jumlah penjumlahan berada dalam urutan log n pada basis 2. Oleh karena itu kesalahan rata-rata akan lebih rendah.


Ini sama dengan menambahkan pasangan yang berdekatan dalam array asli (karena diurutkan). Tidak ada alasan untuk meletakkan semua nilai ke dalam pohon.
Godric Seer

0

Apa yang dikatakan Hristo Iliev di atas tentang kompiler 64-bit yang lebih memilih instruksi SSE dan AVX daripada FPU (AKA NDP) benar-benar benar, setidaknya untuk Microsoft Visual Studio 2013. Namun, untuk operasi floating-point presisi ganda yang saya gunakan, saya menemukan sebenarnya lebih cepat, dan juga secara teori lebih akurat, untuk menggunakan FPU. Jika ini penting bagi Anda, saya sarankan menguji berbagai solusi terlebih dahulu, sebelum memilih pendekatan akhir.

Saat bekerja di Java, saya sangat sering menggunakan tipe data BigDecimal yang presisi-sewenang-wenang. Itu terlalu mudah, dan biasanya orang tidak menyadari kecepatannya menurun. Menghitung fungsi-fungsi transendental dengan deret tak terbatas dan sqrt menggunakan metode Newton dapat memakan waktu milidetik atau lebih, tetapi ini bisa dilakukan dan cukup akurat.


0

Saya hanya meninggalkan ini di sini /programming//a/58006104/860099 (ketika Anda pergi ke sana, klik untuk 'tunjukkan cuplikan kode' dan jalankan dengan tombol

Ini adalah contoh JavaScript yang jelas menunjukkan bahwa jumlah yang dimulai dari yang terbesar memberikan kesalahan yang lebih besar

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

Jawaban hanya tautan tidak disarankan di situs ini. Bisakah Anda menjelaskan apa yang disediakan di tautan?
nicoguaro

@nicoguaro Saya memperbarui jawaban - semua jawaban sangat bagus, tapi di sini adalah contoh nyata
Kamil Kiełczewski
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.