Dalam urutan apa pelampung harus ditambahkan untuk mendapatkan hasil yang paling tepat?

105

Ini adalah pertanyaan yang saya tanyakan pada wawancara saya baru-baru ini dan saya ingin tahu (saya sebenarnya tidak ingat teori analisis numerik, jadi tolong bantu saya :)

Jika kita memiliki beberapa fungsi, yang mengakumulasi bilangan floating-point:

std::accumulate(v.begin(), v.end(), 0.0);

vadalah std::vector<float>, misalnya.

  • Apakah lebih baik mengurutkan angka-angka ini sebelum mengumpulkannya?

  • Urutan mana yang memberikan jawaban paling tepat?

Saya menduga bahwa mengurutkan angka dalam urutan menaik sebenarnya akan mengurangi kesalahan numerik , tetapi sayangnya saya tidak dapat membuktikannya sendiri.

PS Saya menyadari ini mungkin tidak ada hubungannya dengan pemrograman dunia nyata, hanya ingin tahu.

Yippie-Ki-Yay
sumber
17
Ini sebenarnya ada hubungannya dengan pemrograman dunia nyata. Namun, banyak aplikasi tidak terlalu PEDULI tentang keakuratan terbaik absolut dari penghitungan selama 'cukup dekat'. Aplikasi teknik? Sangat penting. Aplikasi medis? Sangat penting. Statistik berskala besar? Akurasi yang sedikit kurang dapat diterima.
Zéychin
18
Harap jangan menjawab kecuali Anda benar-benar tahu dan dapat menunjuk ke halaman yang menjelaskan alasan Anda secara rinci. Sudah ada begitu banyak omong kosong tentang angka floating point terbang di sekitar kita tidak ingin menambahkannya. Jika Anda pikir Anda tahu. BERHENTI. karena jika Anda hanya berpikir Anda tahu maka Anda mungkin salah.
Martin York
4
@ Zéychin "Aplikasi rekayasa? Sangat penting. Aplikasi medis? Sangat penting." ??? Saya pikir Anda akan terkejut jika Anda tahu yang sebenarnya :)
BЈовић
3
@Zeychin Kesalahan mutlak tidak relevan. Yang penting adalah kesalahan relatif. Jika seperseratus radian adalah 0,001%, lalu siapa yang peduli?
BЈовић
3
Saya sangat merekomendasikan bacaan ini: "apa yang perlu diketahui setiap ilmuwan komputer tentang floating point" perso.ens-lyon.fr/jean-michel.muller/goldberg.pdf
Mohammad Alaggan

Jawaban:

108

Naluri Anda pada dasarnya benar, mengurutkan dalam urutan menaik (besarnya) biasanya sedikit meningkatkan banyak hal. Pertimbangkan kasus di mana kita menambahkan pelampung presisi tunggal (32 bit), dan ada 1 miliar nilai yang sama dengan 1 / (1 miliar), dan satu nilai sama dengan 1. Jika 1 datang lebih dulu, maka jumlahnya akan datang menjadi 1, karena 1 + (1/1 miliar) adalah 1 karena hilangnya presisi. Setiap penambahan tidak berpengaruh sama sekali pada total.

Jika nilai kecil datang lebih dulu, mereka setidaknya akan berjumlah sesuatu, meskipun demikian saya memiliki 2 ^ 30 dari mereka, sedangkan setelah 2 ^ 25 atau lebih saya kembali ke situasi di mana masing-masing secara individual tidak mempengaruhi total lagi. Jadi saya masih membutuhkan lebih banyak trik.

Itu kasus yang ekstrem, tetapi secara umum menambahkan dua nilai dengan besaran yang sama lebih akurat daripada menambahkan dua nilai dengan besaran yang sangat berbeda, karena Anda "membuang" lebih sedikit bit presisi dalam nilai yang lebih kecil dengan cara itu. Dengan mengurutkan angka-angka, Anda mengelompokkan nilai-nilai yang besarnya sama, dan dengan menambahkannya dalam urutan menaik Anda memberi nilai-nilai kecil sebuah "peluang" untuk secara kumulatif mencapai besaran angka yang lebih besar.

Namun, jika angka negatif terlibat, mudah untuk "mengecoh" pendekatan ini. Pertimbangkan tiga nilai untuk dijumlahkan {1, -1, 1 billionth},. Jumlah yang benar secara aritmatika adalah 1 billionth, tetapi jika penjumlahan pertama saya melibatkan nilai kecil maka jumlah akhir saya adalah 0. Dari 6 kemungkinan pesanan, hanya 2 yang "benar" - {1, -1, 1 billionth}dan{-1, 1, 1 billionth} . Semua 6 pesanan memberikan hasil yang akurat pada skala nilai besaran terbesar di masukan (0,0000001% keluar), tetapi untuk 4 dari mereka hasilnya tidak akurat pada skala solusi sebenarnya (100% keluar). Masalah khusus yang Anda selesaikan akan memberi tahu Anda apakah yang pertama cukup baik atau tidak.

Faktanya, Anda dapat memainkan lebih banyak trik daripada hanya menambahkannya dalam urutan yang diurutkan. Jika Anda memiliki banyak nilai yang sangat kecil, angka tengah dari nilai sedang, dan sejumlah kecil nilai besar, maka mungkin paling akurat untuk pertama-tama menjumlahkan semua yang kecil, lalu secara terpisah menjumlahkan yang sedang, tambahkan kedua total tersebut bersama-sama lalu tambahkan yang besar. Sama sekali tidak sepele untuk menemukan kombinasi paling akurat dari penambahan floating-point, tetapi untuk mengatasi kasus yang sangat buruk, Anda dapat menyimpan seluruh rangkaian total yang berjalan pada besaran yang berbeda, tambahkan setiap nilai baru ke total yang paling sesuai dengan besarnya, dan saat total berjalan mulai terlalu besar untuk besarannya, tambahkan ke total berikutnya dan mulai yang baru. Diambil ke ekstrem logisnya, proses ini setara dengan melakukan penjumlahan dalam tipe presisi sewenang-wenang (jadi Anda ' d melakukan itu). Tetapi mengingat pilihan sederhana untuk menambahkan dalam urutan naik atau turun, naik adalah taruhan yang lebih baik.

Ini memang memiliki beberapa hubungan dengan pemrograman dunia nyata, karena ada beberapa kasus di mana perhitungan Anda bisa menjadi sangat salah jika Anda secara tidak sengaja memotong ekor "berat" yang terdiri dari sejumlah besar nilai yang masing-masing terlalu kecil untuk mempengaruhi satu per satu. jumlahnya, atau jika Anda membuang terlalu banyak presisi dari banyak nilai kecil yang secara individual hanya memengaruhi beberapa bit terakhir dari jumlah tersebut. Dalam kasus di mana ekor dapat diabaikan, Anda mungkin tidak peduli. Misalnya jika Anda hanya menjumlahkan sejumlah kecil nilai di tempat pertama dan Anda hanya menggunakan beberapa angka penting dari jumlah tersebut.

Steve Jessop
sumber
8
1 untuk penjelasan. Ini agak kontra-intuitif karena penjumlahan biasanya stabil secara numerik (tidak seperti pengurangan dan pembagian).
Konrad Rudolph
2
@Konrad, mungkin numeriknya stabil, tetapi tidak tepat mengingat besaran operan yang berbeda :)
MSN
3
@ 6502: mereka diurutkan dalam urutan besarnya, jadi -1 muncul di akhir. Jika nilai sebenarnya dari total adalah besarnya 1, maka tidak masalah. Jika Anda menjumlahkan tiga nilai: 1 / miliar, 1 dan -1, maka Anda akan mendapatkan 0, di mana Anda harus menjawab pertanyaan praktis yang menarik - apakah Anda memerlukan jawaban yang akurat pada skala jumlah sebenarnya, atau apakah Anda hanya membutuhkan jawaban yang akurat pada skala nilai terbesar? Untuk beberapa aplikasi praktis, yang terakhir sudah cukup baik, tetapi jika tidak, Anda memerlukan pendekatan yang lebih canggih. Fisika kuantum menggunakan renormalisasi.
Steve Jessop
8
Jika Anda akan tetap menggunakan skema sederhana ini, saya akan selalu menambahkan dua angka dengan besaran terendah dan memasukkan kembali jumlahnya dalam set. (Yah, mungkin jenis gabungan akan bekerja paling baik di sini. Anda dapat menggunakan bagian dari larik yang berisi angka yang telah dijumlahkan sebelumnya sebagai area kerja untuk jumlah parsial.)
Neil
2
@Kevin Panko: Versi sederhananya adalah bahwa float presisi tunggal memiliki 24 digit biner, yang terbesar adalah bit set terbesar dalam angkanya. Jadi jika Anda menjumlahkan dua angka yang berbeda besarnya lebih dari 2 ^ 24, Anda akan mengalami kerugian total dari nilai yang lebih kecil, dan jika jumlahnya berbeda dengan derajat yang lebih kecil maka Anda kehilangan jumlah bit yang sesuai dari akurasi yang lebih kecil. jumlah.
Steve Jessop
88

Ada juga algoritma yang dirancang untuk operasi akumulasi semacam ini, yang disebut Penjumlahan Kahan , yang mungkin harus Anda ketahui.

Menurut Wikipedia,

The algoritma penjumlahan Kahan (juga dikenal sebagai penjumlahan kompensasi ) secara signifikan mengurangi kesalahan numerik dalam total diperoleh dengan menambahkan urutan angka floating point presisi yang terbatas, dibandingkan dengan pendekatan yang jelas. Ini dilakukan dengan menjaga kompensasi berjalan terpisah (variabel untuk mengakumulasi kesalahan kecil).

Dalam pseudocode, algoritmanya adalah:

function kahanSum(input)
 var sum = input[1]
 var c = 0.0          //A running compensation for lost low-order bits.
 for i = 2 to input.length
  y = input[i] - c    //So far, so good: c is zero.
  t = sum + y         //Alas, sum is big, y small, so low-order digits of y are lost.
  c = (t - sum) - y   //(t - sum) recovers the high-order part of y; subtracting y recovers -(low part of y)
  sum = t             //Algebraically, c should always be zero. Beware eagerly optimising compilers!
 next i               //Next time around, the lost low part will be added to y in a fresh attempt.
return sum
Daniel Pryden
sumber
3
+1 tambahan yang bagus untuk utas ini. Setiap kompiler yang "mengoptimalkan" pernyataan tersebut harus dilarang.
Chris A.14
1
Ini adalah metode sederhana untuk menggandakan presisi, dengan menggunakan dua variabel penjumlahan sumdan cdengan besaran yang berbeda. Ini dapat diperpanjang dengan mudah ke variabel N.
MSalters
2
@Tokopedia nah, Anda dapat mengontrol ini secara eksplisit pada semua kompiler yang dihitung (mis. via -ffast-mathdi GCC).
Konrad Rudolph
6
@Konrad Rudol terima kasih telah menunjukkan bahwa ini adalah kemungkinan pengoptimalan dengan -ffast-math. Apa yang saya pelajari dari diskusi ini dan tautan ini , adalah bahwa jika Anda peduli dengan keakuratan numerik, Anda mungkin harus menghindari penggunaan -ffast-mathtetapi di banyak aplikasi di mana Anda mungkin terikat CPU tetapi tidak peduli dengan perhitungan numerik yang tepat, (pemrograman game misalnya ), -ffast-mathwajar untuk digunakan. Karena itu, saya ingin mengubah komentar saya yang "dilarang".
Chris A.
Menggunakan variabel presisi ganda untuk sum, c, t, yakan membantu. Anda juga perlu menambahkan sum -= csebelumnya return sum.
G. Cohen
34

Saya mencoba contoh ekstrim dalam jawaban yang diberikan oleh Steve Jessop.

#include <iostream>
#include <iomanip>
#include <cmath>

int main()
{
    long billion = 1000000000;
    double big = 1.0;
    double small = 1e-9;
    double expected = 2.0;

    double sum = big;
    for (long i = 0; i < billion; ++i)
        sum += small;
    std::cout << std::scientific << std::setprecision(1) << big << " + " << billion << " * " << small << " = " <<
        std::fixed << std::setprecision(15) << sum <<
        "    (difference = " << std::fabs(expected - sum) << ")" << std::endl;

    sum = 0;
    for (long i = 0; i < billion; ++i)
        sum += small;
    sum += big;
    std::cout  << std::scientific << std::setprecision(1) << billion << " * " << small << " + " << big << " = " <<
        std::fixed << std::setprecision(15) << sum <<
        "    (difference = " << std::fabs(expected - sum) << ")" << std::endl;

    return 0;
}

Saya mendapatkan hasil sebagai berikut:

1.0e+00 + 1000000000 * 1.0e-09 = 2.000000082740371    (difference = 0.000000082740371)
1000000000 * 1.0e-09 + 1.0e+00 = 1.999999992539933    (difference = 0.000000007460067)

Kesalahan di baris pertama lebih dari sepuluh kali lebih besar di baris kedua.

Jika saya mengubah doubles menjadi floats pada kode di atas, saya mendapatkan:

1.0e+00 + 1000000000 * 1.0e-09 = 1.000000000000000    (difference = 1.000000000000000)
1000000000 * 1.0e-09 + 1.0e+00 = 1.031250000000000    (difference = 0.968750000000000)

Tidak ada jawaban yang bahkan mendekati 2.0 (tetapi yang kedua sedikit lebih dekat).

Menggunakan penjumlahan Kahan (dengan doubles) seperti yang dijelaskan oleh Daniel Pryden:

#include <iostream>
#include <iomanip>
#include <cmath>

int main()
{
    long billion = 1000000000;
    double big = 1.0;
    double small = 1e-9;
    double expected = 2.0;

    double sum = big;
    double c = 0.0;
    for (long i = 0; i < billion; ++i) {
        double y = small - c;
        double t = sum + y;
        c = (t - sum) - y;
        sum = t;
    }

    std::cout << "Kahan sum  = " << std::fixed << std::setprecision(15) << sum <<
        "    (difference = " << std::fabs(expected - sum) << ")" << std::endl;

    return 0;
}

Saya mendapatkan persis 2.0:

Kahan sum  = 2.000000000000000    (difference = 0.000000000000000)

Dan bahkan jika saya mengubah doubles menjadi floats pada kode di atas, saya mendapatkan:

Kahan sum  = 2.000000000000000    (difference = 0.000000000000000)

Tampaknya Kahan adalah jalan yang harus ditempuh!

Andrew Stein
sumber
Nilai "besar" saya sama dengan 1, bukan 1e9. Jawaban kedua Anda, ditambahkan dalam urutan ukuran yang semakin besar, adalah benar secara matematis (1 miliar, ditambah satu miliar miliar, adalah 1 miliar dan 1), meskipun lebih beruntung ada tingkat kesehatan umum metode ini :-) Perhatikan bahwa doubletidak buruk kehilangan ketepatan dalam menambahkan bersama-sama satu miliar miliar, karena ia memiliki 52 bit signifikan, sedangkan IEEE floathanya memiliki 24 dan akan.
Steve Jessop
@ Steve, salahku, maaf. Saya telah memperbarui kode contoh sesuai keinginan Anda.
Andrew Stein
4
Kahan masih memiliki presisi yang terbatas, tetapi untuk membuat kasus pembunuh, Anda memerlukan jumlah utama dan akumulator error cuntuk memuat nilai yang jauh lebih besar daripada ringkasan berikutnya. Ini berarti jumlah penjumlahannya jauh, jauh lebih kecil daripada jumlah utama, jadi harus ada banyak sekali untuk dijumlahkan. Apalagi dengan doublearitmatika.
Steve Jessop
14

Ada kelas algoritme yang menyelesaikan masalah ini secara tepat, tanpa perlu mengurutkan atau menyusun ulang data .

Dengan kata lain, penjumlahan dapat dilakukan dalam satu kali lintasan data. Hal ini juga membuat algoritme semacam itu dapat diterapkan dalam situasi di mana kumpulan data tidak diketahui sebelumnya, misalnya jika data tiba dalam waktu nyata dan jumlah yang berjalan perlu dipertahankan.

Berikut adalah abstrak makalah terbaru:

Kami menyajikan algoritme online baru untuk penjumlahan yang tepat dari aliran bilangan floating-point. Yang kami maksud dengan "online" adalah algoritme hanya perlu melihat satu masukan pada satu waktu, dan dapat mengambil aliran masukan dengan panjang sembarang dari masukan tersebut sementara hanya membutuhkan memori yang konstan. Yang kami maksud dengan "tepat" adalah jumlah array internal algoritme kami persis sama dengan jumlah semua input, dan hasil yang dikembalikan adalah jumlah yang dibulatkan dengan benar. Bukti kebenaran berlaku untuk semua masukan (termasuk bilangan nonnormalisasi tetapi luapan perantara modulo), dan tidak bergantung pada jumlah penjumlahan atau jumlah kondisi dari penjumlahan. Algoritme secara asimtotik hanya membutuhkan 5 FLOP per sumand, dan karena paralelisme level instruksi berjalan hanya sekitar 2-3 kali lebih lambat dari yang sudah jelas, fast-but-dumb “ordinary recursive sumation” loop ketika jumlah penjumlahan lebih dari 10.000. Jadi, sepengetahuan kami, ini adalah yang tercepat, paling akurat, dan paling hemat memori di antara algoritme yang dikenal. Memang, sulit untuk melihat bagaimana algoritme yang lebih cepat atau algoritme yang membutuhkan FLOP yang jauh lebih sedikit bisa ada tanpa peningkatan perangkat keras. Aplikasi untuk sejumlah besar ringkasan disediakan.

Sumber: Algoritma 908: Penjumlahan Tepat Online Arus Titik Mengambang .

NPE
sumber
1
@Inverse: Masih ada perpustakaan fisik di sekitar. Atau, membeli PDF online berharga $ 5- $ 15 (tergantung apakah Anda anggota ACM). Terakhir, DeepDyve tampaknya menawarkan untuk meminjamkan koran selama 24 jam seharga $ 2,99 (jika Anda baru mengenal DeepDyve, Anda bahkan mungkin bisa mendapatkannya secara gratis sebagai bagian dari uji coba gratis mereka): deepdyve.com/lp/acm /…
NPE
2

Berdasarkan jawaban Steve untuk pertama-tama mengurutkan angka-angka dalam urutan menaik, saya akan memperkenalkan dua gagasan lagi:

  1. Tentukan perbedaan eksponen dua angka di atas yang mungkin Anda putuskan bahwa Anda akan kehilangan terlalu banyak presisi.

  2. Kemudian tambahkan angkanya secara berurutan hingga eksponen akumulator terlalu besar untuk bilangan berikutnya, lalu letakkan akumulator ke antrean sementara dan mulai akumulator dengan bilangan berikutnya. Lanjutkan sampai Anda kehabisan daftar aslinya.

Anda mengulangi proses dengan antrian sementara (setelah mengurutkannya) dan dengan perbedaan eksponen yang mungkin lebih besar.

Saya rasa ini akan sangat lambat jika Anda harus menghitung eksponen sepanjang waktu.

Saya dengan cepat pergi dengan sebuah program dan hasilnya adalah 1,99903

quamrana
sumber
2

Saya pikir Anda bisa melakukan lebih baik daripada menyortir angka sebelum Anda mengumpulkannya, karena selama proses akumulasi, akumulator menjadi semakin besar. Jika Anda memiliki banyak angka serupa, Anda akan mulai kehilangan presisi dengan cepat. Inilah yang saya sarankan:

while the list has multiple elements
    remove the two smallest elements from the list
    add them and put the result back in
the single element in the list is the result

Tentu saja algoritma ini akan paling efisien dengan antrian prioritas daripada daftar. Kode C ++:

template <typename Queue>
void reduce(Queue& queue)
{
    typedef typename Queue::value_type vt;
    while (queue.size() > 1)
    {
        vt x = queue.top();
        queue.pop();
        vt y = queue.top();
        queue.pop();
        queue.push(x + y);
    }
}

sopir:

#include <iterator>
#include <queue>

template <typename Iterator>
typename std::iterator_traits<Iterator>::value_type
reduce(Iterator begin, Iterator end)
{
    typedef typename std::iterator_traits<Iterator>::value_type vt;
    std::priority_queue<vt> positive_queue;
    positive_queue.push(0);
    std::priority_queue<vt> negative_queue;
    negative_queue.push(0);
    for (; begin != end; ++begin)
    {
        vt x = *begin;
        if (x < 0)
        {
            negative_queue.push(x);
        }
        else
        {
            positive_queue.push(-x);
        }
    }
    reduce(positive_queue);
    reduce(negative_queue);
    return negative_queue.top() - positive_queue.top();
}

Angka dalam antrian negatif karena topmenghasilkan angka terbesar , tetapi kita menginginkan yang terkecil . Saya bisa memberikan lebih banyak argumen template ke antrian, tetapi pendekatan ini tampaknya lebih sederhana.

fredoverflow
sumber
2

Ini tidak cukup menjawab pertanyaan Anda, tetapi hal yang cerdas untuk dilakukan adalah menjalankan penjumlahan dua kali, sekali dengan mode pembulatan " pembulatan ke atas" dan sekali dengan "pembulatan ke bawah". Bandingkan kedua jawaban tersebut, dan Anda tahu / bagaimana / tidak akurat hasil Anda, dan oleh karena itu Anda perlu menggunakan strategi penjumlahan yang lebih cerdas. Sayangnya, sebagian besar bahasa tidak membuat perubahan mode pembulatan floating point semudah yang seharusnya, karena orang tidak tahu bahwa itu sebenarnya berguna dalam perhitungan sehari-hari.

Lihatlah aritmatika Interval di mana Anda melakukan semua matematika seperti ini, menjaga nilai tertinggi dan terendah saat Anda pergi. Ini mengarah pada beberapa hasil dan optimisasi yang menarik.

rjmunro
sumber
0

Yang paling sederhana semacam yang meningkatkan akurasi untuk mengurutkan berdasarkan nilai absolut naik. Itu memungkinkan nilai magnitudo terkecil memiliki kesempatan untuk mengakumulasi atau membatalkan sebelum berinteraksi dengan nilai magnitudo yang lebih besar yang akan memicu hilangnya presisi.

Meskipun demikian, Anda dapat melakukan lebih baik dengan melacak beberapa jumlah parsial yang tidak tumpang tindih. Berikut adalah makalah yang menjelaskan teknik dan menyajikan bukti akurasi: www-2.cs.cmu.edu/afs/cs/project/quake/public/papers/robust-arithmetic.ps

Algoritme tersebut dan pendekatan lain untuk penjumlahan titik mengambang yang tepat diimplementasikan dengan Python sederhana di: http://code.activestate.com/recipes/393090/ Setidaknya dua di antaranya dapat diubah dengan mudah ke C ++.

Raymond Hettinger
sumber
0

Untuk IEEE 754 presisi tunggal atau ganda atau nomor format yang diketahui, alternatif lain adalah menggunakan larik angka (diteruskan oleh pemanggil, atau dalam kelas untuk C ++) yang diindeks oleh eksponen. Saat menambahkan angka ke dalam array, hanya angka dengan eksponen yang sama yang ditambahkan (sampai slot kosong ditemukan dan angka disimpan). Saat penjumlahan diminta, larik dijumlahkan dari terkecil ke terbesar untuk meminimalkan pemotongan. Contoh presisi tunggal:

/* clear array */
void clearsum(float asum[256])
{
size_t i;
    for(i = 0; i < 256; i++)
        asum[i] = 0.f;
}

/* add a number into array */
void addtosum(float f, float asum[256])
{
size_t i;
    while(1){
        /* i = exponent of f */
        i = ((size_t)((*(unsigned int *)&f)>>23))&0xff;
        if(i == 0xff){          /* max exponent, could be overflow */
            asum[i] += f;
            return;
        }
        if(asum[i] == 0.f){     /* if empty slot store f */
            asum[i] = f;
            return;
        }
        f += asum[i];           /* else add slot to f, clear slot */
        asum[i] = 0.f;          /* and continue until empty slot */
    }
}

/* return sum from array */
float returnsum(float asum[256])
{
float sum = 0.f;
size_t i;
    for(i = 0; i < 256; i++)
        sum += asum[i];
    return sum;
}

contoh presisi ganda:

/* clear array */
void clearsum(double asum[2048])
{
size_t i;
    for(i = 0; i < 2048; i++)
        asum[i] = 0.;
}

/* add a number into array */
void addtosum(double d, double asum[2048])
{
size_t i;
    while(1){
        /* i = exponent of d */
        i = ((size_t)((*(unsigned long long *)&d)>>52))&0x7ff;
        if(i == 0x7ff){         /* max exponent, could be overflow */
            asum[i] += d;
            return;
        }
        if(asum[i] == 0.){      /* if empty slot store d */
            asum[i] = d;
            return;
        }
        d += asum[i];           /* else add slot to d, clear slot */
        asum[i] = 0.;           /* and continue until empty slot */
    }
}

/* return sum from array */
double returnsum(double asum[2048])
{
double sum = 0.;
size_t i;
    for(i = 0; i < 2048; i++)
        sum += asum[i];
    return sum;
}
rcgldr.dll
sumber
Ini terdengar seperti metode Malcolm 1971 atau, lebih tepatnya, variannya yang menggunakan eksponen Demmel dan Hida ("Algoritma 3"). Ada algoritme lain di luar sana yang melakukan loop berbasis carry seperti milik Anda, tetapi saya tidak dapat menemukannya saat ini.
ZachB
@ZachB - konsepnya mirip dengan bottom up merge sort untuk daftar tertaut , yang juga menggunakan array kecil, di mana array [i] menunjuk ke daftar dengan 2 ^ i node. Saya tidak tahu sejauh mana ini berjalan. Dalam kasus saya, itu adalah penemuan jati diri di tahun 1970-an.
rcgldr
-1

Pelampung Anda harus ditambahkan dengan presisi ganda. Itu akan memberi Anda lebih banyak presisi tambahan daripada teknik lainnya. Untuk presisi yang lebih tinggi dan kecepatan yang jauh lebih tinggi, Anda dapat membuat katakan empat penjumlahan, dan menjumlahkannya di akhir.

Jika Anda menambahkan angka presisi ganda, gunakan double panjang untuk penjumlahannya - namun, ini hanya akan berdampak positif dalam implementasi di mana long double sebenarnya memiliki presisi lebih dari double (biasanya x86, PowerPC bergantung pada pengaturan compiler).

gnasher729
sumber
1
"Itu akan memberi Anda lebih banyak ketelitian daripada teknik lain yang bisa" Apakah Anda menyadari bahwa jawaban Anda datang lebih dari satu tahun setelah jawaban terlambat sebelumnya yang menjelaskan bagaimana menggunakan penjumlahan yang tepat?
Pascal Cuoq
Tipe "double panjang" sangat buruk dan Anda tidak boleh menggunakannya.
Jeff
-1

Mengenai pengurutan, menurut saya, jika Anda mengharapkan pembatalan maka angka-angka tersebut harus ditambahkan dalam urutan besaran turun , bukan naik. Misalnya:

((-1 + 1) + 1e-20) akan menghasilkan 1e-20

tapi

((1e-20 + 1) - 1) akan menghasilkan 0

Dalam persamaan pertama, dua bilangan besar ditiadakan, sedangkan pada persamaan kedua suku 1e-20 hilang jika ditambahkan ke 1, karena tidak cukup presisi untuk mempertahankannya.

Selain itu, penjumlahan berpasangan cukup baik untuk menjumlahkan banyak angka.

KOAD
sumber