Menganalisis Kesalahan Angka dalam Fungsi C ++

20

Misalkan saya memiliki fungsi yang mengambil input beberapa nilai floating-point (tunggal atau ganda), melakukan beberapa perhitungan, dan menghasilkan output nilai floating-point (juga tunggal atau ganda). Saya bekerja terutama dengan MSVC 2008, tetapi juga berencana untuk bekerja dengan MinGW / GCC. Saya pemrograman dalam C ++.

Apa cara khas mengukur secara program seberapa banyak kesalahan dalam hasil? Dengan asumsi bahwa saya perlu menggunakan pustaka presisi arbitrer: perpustakaan seperti apa yang terbaik jika saya tidak peduli dengan kecepatan?

user_123abc
sumber

Jawaban:

17

Jika Anda mencari batasan yang baik pada kesalahan pembulatan Anda, Anda tidak perlu pustaka presisi aribtrary. Anda dapat menggunakan analisis kesalahan yang sedang berjalan.

Saya tidak dapat menemukan referensi online yang bagus, tetapi semuanya dijelaskan dalam Bagian 3.3 buku Nick Higham "Akurasi dan Stabilitas Algoritma Numerik". Idenya cukup sederhana:

  1. Re-faktor kode Anda sehingga Anda memiliki satu tugas operasi aritmatika tunggal di setiap baris.
  2. Untuk setiap variabel, misalnya x, buat variabel x_erryang diinisialisasi ke nol ketika xdiberi konstanta.
  3. Untuk setiap operasi, misalnya z = x * y, memperbarui variabel z_errmenggunakan model standar aritmatika floating-point dan yang dihasilkan zkesalahan dan berjalan x_errdan y_err.
  4. Nilai kembalinya fungsi Anda juga harus memiliki _errnilai terkait. Ini adalah ketergantungan data yang terikat pada kesalahan pembulatan total Anda.

Bagian yang sulit adalah langkah 3. Untuk operasi aritmatika yang paling sederhana, Anda dapat menggunakan aturan berikut:

  • z = x + y -> z_err = u*abs(z) + x_err + y_err
  • z = x - y -> z_err = u*abs(z) + x_err + y_err
  • z = x * y -> z_err = u*abs(z) + x_err*abs(y) + y_err*abs(x)
  • z = x / y -> z_err = u*abs(z) + (x_err*abs(y) + y_err*abs(x))/y^2
  • z = sqrt(x) -> z_err = u*abs(z) + x_err/(2*abs(z))

di mana u = eps/2unit pembulatan. Ya, aturan untuk +dan -sama. Aturan untuk operasi lain op(x)dapat dengan mudah diekstraksi menggunakan ekspansi seri Taylor dari hasil yang diterapkan op(x + x_err). Atau Anda dapat mencoba googling. Atau menggunakan buku Nick Higham.

Sebagai contoh, pertimbangkan kode Matlab / Oktaf berikut yang mengevaluasi polinomial dalam koefisien apada suatu titik xmenggunakan skema Horner:

function s = horner ( a , x )
    s = a(end);
    for k=length(a)-1:-1:1
        s = a(k) + x*s;
    end

Untuk langkah pertama, kami membagi dua operasi di s = a(k) + x*s:

function s = horner ( a , x )
    s = a(end);
    for k=length(a)-1:-1:1
        z = x*s;
        s = a(k) + z;
    end

Kami kemudian memperkenalkan _errvariabel. Perhatikan bahwa input adan xdianggap tepat, tetapi kita juga bisa meminta pengguna untuk memberikan nilai yang sesuai untuk a_errdan x_err:

function [ s , s_err ] = horner ( a , x )
    s = a(end);
    s_err = 0;
    for k=length(a)-1:-1:1
        z = x*s;
        z_err = ...;
        s = a(k) + z;
        s_err = ...;
    end

Akhirnya, kami menerapkan aturan yang dijelaskan di atas untuk mendapatkan persyaratan kesalahan:

function [ s , s_err ] = horner ( a , x )
    u = eps/2;
    s = a(end);
    s_err = 0;
    for k=length(a)-1:-1:1
        z = x*s;
        z_err = u*abs(z) + s_err*abs(x);
        s = a(k) + z;
        s_err = u*abs(s) + z_err;
    end

Perhatikan bahwa karena kita tidak memiliki a_erratau x_err, misalnya mereka dianggap nol, istilah masing-masing hanya diabaikan dalam ekspresi kesalahan.

Dan lagi! Kami sekarang memiliki skema Horner yang mengembalikan perkiraan kesalahan yang bergantung pada data (catatan: ini adalah batas atas kesalahan) di samping hasilnya.

Sebagai catatan, karena Anda menggunakan C ++, Anda dapat mempertimbangkan membuat kelas Anda sendiri untuk nilai-nilai floating-point yang membawa sekitar _erristilah dan membebani semua operasi aritmatika untuk memperbarui nilai-nilai ini seperti yang dijelaskan di atas. Untuk kode besar, ini mungkin rute yang lebih mudah, meskipun secara komputasi kurang efisien. Karena itu, Anda mungkin dapat menemukan kelas online semacam itu. Pencarian Google cepat memberi saya tautan ini .

PS Perhatikan bahwa ini semua hanya bekerja pada mesin yang hanya mematuhi IEEE-754, yaitu semua operasi aritmatika tepat untuk . Analisis ini juga memberikan ikatan yang lebih ketat dan lebih realistis daripada menggunakan aritmatika interval karena, menurut definisi, Anda tidak dapat mewakili angka di titik mengambang, yaitu interval Anda hanya akan membulatkan ke angka itu sendiri.±kamux(1±kamu)

Pedro
sumber
1
+1 untuk analisis ini, karena menarik. Saya suka pekerjaan Higham. Yang menjadi perhatian saya adalah bahwa mengharuskan pengguna untuk menulis kode tambahan itu dengan tangan (alih-alih semi-otomatis seperti interval aritmatika) dapat menjadi rawan kesalahan karena jumlah operasi numerik menjadi besar.
Geoff Oxberry
1
@ GeoffOxberry: Saya sepenuhnya setuju dengan masalah kompleksitas. Untuk kode yang lebih besar, saya sangat menyarankan untuk menulis kelas / tipe data yang membebani operasi pada ganda seperti hanya perlu menerapkan setiap operasi dengan benar sekali. Saya cukup terkejut bahwa sepertinya tidak ada yang seperti ini untuk Matlab / Oktaf.
Pedro
Saya suka analisis ini, tetapi karena perhitungan istilah kesalahan juga dilakukan dalam floating-point, tidakkah istilah kesalahan tersebut tidak tepat karena kesalahan floating-point?
plasmacel
8

Sebuah pustaka portabel dan sumber terbuka yang bagus untuk aritmatika floating point presisi sewenang-wenang (dan banyak lagi lainnya) adalah NTL Victor Shoup , yang tersedia dalam bentuk sumber C ++.

Pada tingkat yang lebih rendah adalah Bignum Library GNU Multiple Precision (GMP) , juga merupakan paket sumber terbuka.

NTL dapat digunakan dengan GMP jika diperlukan kinerja yang lebih cepat, tetapi NTL menyediakan rutinitas basisnya sendiri yang tentu dapat digunakan jika Anda "tidak peduli dengan kecepatan". GMP mengklaim sebagai "perpustakaan bignum tercepat". GMP sebagian besar ditulis dalam bahasa C, tetapi memiliki antarmuka C ++.

Ditambahkan: Sementara interval aritmatika dapat memberikan batas atas dan bawah pada jawaban yang tepat dengan cara otomatis, ini tidak secara akurat mengukur kesalahan dalam perhitungan presisi "standar" karena ukuran interval biasanya tumbuh dengan setiap operasi (baik dalam relatif atau pengertian kesalahan absolut).

Cara khas untuk menemukan ukuran kesalahan, baik untuk kesalahan pembulatan atau untuk kesalahan diskritisasi, dll., Adalah untuk menghitung nilai presisi ekstra dan membandingkannya dengan nilai presisi "standar". Hanya presisi ekstra sederhana yang diperlukan untuk menentukan ukuran kesalahan itu sendiri untuk akurasi yang masuk akal, karena kesalahan pembulatan saja secara substansial lebih besar dalam presisi "standar" daripada mereka dalam perhitungan presisi ekstra.

Intinya dapat diilustrasikan dengan membandingkan perhitungan presisi tunggal dan ganda. Perhatikan bahwa dalam ekspresi menengah C ++ selalu dihitung dalam (setidaknya) presisi ganda, jadi jika kita ingin menggambarkan seperti apa komputasi dalam presisi tunggal "murni" itu, kita perlu menyimpan nilai-nilai perantara dalam presisi tunggal.

Cuplikan kode C

    float fa,fb;
    double da,db,err;
    fa = 4.0;
    fb = 3.0;
    fa = fa/fb;
    fa -= 1.0;

    da = 4.0;
    db = 3.0;
    da = da/db;
    da -= 1.0;

    err = fa - da;
    printf("Single precision error wrt double precision value\n");
    printf("Error in getting 1/3rd is %e\n",err);
    return 0;

Output dari atas (rantai alat Cygwin / MinGW32 GCC):

Single precision error wrt double precision value
Error in getting 1/3rd is 3.973643e-08

Jadi kesalahannya adalah tentang apa yang diharapkan dalam pembulatan 1/3 ke presisi tunggal. Seseorang tidak akan (saya curiga) peduli untuk mendapatkan lebih dari beberapa tempat desimal dalam kesalahan yang benar, karena pengukuran kesalahan adalah untuk besarnya dan bukan untuk ketepatan.

hardmath
sumber
Pendekatan Anda benar-benar baik secara matematis. Saya pikir pengorbanannya sangat ketat; orang-orang yang bertele-tele tentang kesalahan akan menunjukkan keketatan aritmatika interval, tetapi saya menduga bahwa dalam banyak aplikasi, komputasi dengan presisi ekstra akan cukup, dan perkiraan kesalahan yang dihasilkan kemungkinan akan lebih ketat, seperti yang Anda tunjukkan.
Geoff Oxberry
Ini adalah pendekatan yang saya bayangkan akan saya gunakan. Saya mungkin mencoba beberapa teknik berbeda ini untuk melihat mana yang paling tepat untuk aplikasi saya. Pembaruan contoh kode sangat dihargai!
user_123abc
7

GMP (yaitu, GNU Multiple Precision library) adalah perpustakaan presisi arbitrer terbaik yang saya tahu.

Saya tidak tahu cara terprogram untuk mengukur kesalahan dalam hasil fungsi floating point arbitrer. Satu hal yang bisa Anda coba adalah menghitung perpanjangan interval dari suatu fungsi menggunakan interval aritmatika . Di C ++, Anda harus menggunakan semacam perpustakaan untuk menghitung ekstensi interval; salah satu perpustakaan tersebut adalah Boost Interval Arithmetic Library. Pada dasarnya, untuk mengukur kesalahan, Anda akan menyediakan sebagai argumen untuk interval fungsi Anda yang memiliki lebar 2 kali pembulatan unit (kira-kira), berpusat pada nilai-nilai yang menarik, dan kemudian output Anda akan menjadi kumpulan interval, lebar yang akan memberi Anda beberapa perkiraan kesalahan yang konservatif. Kesulitan dengan pendekatan ini adalah bahwa aritmatika interval yang digunakan dalam mode ini dapat melebih-lebihkan kesalahan dengan jumlah yang signifikan, tetapi pendekatan ini adalah yang paling "terprogram" yang bisa saya pikirkan.

Geoff Oxberry
sumber
Ah, saya baru saja memperhatikan interval aritmatika yang disebutkan dalam jawaban Anda ... Terpilih!
Ali
2
Richard Harris menulis serangkaian artikel yang luar biasa dalam jurnal ACCU Overload tentang Floating Point Blues . Artikelnya tentang interval aritmatika ada di Overload 103 ( pdf , p19-24).
Mark Booth
6

Estimasi kesalahan yang ketat dan otomatis dapat dicapai dengan analisis interval . Anda bekerja dengan interval alih-alih angka. Misalnya penambahan:

[a,b] + [c,d] = [min(a+c, a+d, b+c, b+d), max (a+c, a+d, b+c, b+d)] = [a+c, b+d]

Pembulatan juga dapat ditangani dengan ketat, lihat Rounded interval aritmatika .

Selama input Anda terdiri dari interval yang sempit, estimasi tersebut OK dan setiap hari murah untuk dihitung. Sayangnya, kesalahan sering ditaksir berlebihan, lihat masalah ketergantungan .

Saya tidak tahu ada perpustakaan aritmatika interval presisi sewenang-wenang.

Tergantung pada masalah Anda, apakah interval aritmatika dapat memenuhi kebutuhan Anda atau tidak.

Ali
sumber
4

The GNU MPFR perpustakaan adalah mengambang perpustakaan sewenang-wenang-presisi yang memiliki akurasi tinggi (khususnya, pembulatan benar untuk semua operasi, yang tidak semudah kedengarannya) sebagai salah satu titik fokus utama mereka. Ini menggunakan GNU MP di bawah tenda. Ini memiliki ekstensi yang disebut MPFI yang melakukan aritmatika interval, yang - seperti jawaban Geoff - mungkin berguna untuk keperluan verifikasi: terus meningkatkan presisi kerja sampai interval yang dihasilkan jatuh dalam batas kecil.

Ini tidak akan selalu berhasil; khususnya itu belum tentu efektif jika Anda melakukan sesuatu seperti integrasi numerik, di mana setiap langkah membawa "kesalahan" terlepas dari masalah pembulatan. Dalam hal itu, coba paket khusus seperti COZY infinity yang melakukan ini dengan sangat baik menggunakan algoritma spesifik untuk mengikat kesalahan integrasi (dan menggunakan apa yang disebut model Taylor alih-alih interval).

Erik P.
sumber
Saya setuju; integrasi numerik jelas merupakan kasus di mana aritmatika interval naif dapat serba salah. Namun, bahkan model Taylor menggunakan aritmatika interval. Saya akrab dengan karya Makino dan Berz, dan saya percaya mereka menggunakan model Taylor dalam arti RE Moore, meskipun mereka juga menggunakan trik yang melibatkan apa yang mereka sebut "aljabar diferensial".
Geoff Oxberry
@ GeoffOxberry: Ya - Saya pikir aljabar diferensial ini adalah hal untuk terikat pada langkah integrasi.
Erik P.
0

Saya diberitahu bahwa MPIR adalah perpustakaan yang baik untuk digunakan jika Anda bekerja dengan Visual Studio:

http://mpir.org/

nelayan itu
sumber
Selamat datang di SciComp.SE! Bisakah Anda menambahkan beberapa detail tentang bagaimana perpustakaan ini dapat digunakan untuk mengukur kesalahan perhitungan floating point?
Christian Clason
Saya akan mencoba; Saya sebenarnya belum mengatur MPIR di komputer saya dulu! Saya sudah menyiapkan GMP dan MPFR.
nelayan bahwa