Analisi dell'errore numerico nella funzione C ++


20

Supponiamo che io abbia una funzione che accetta come input diversi valori in virgola mobile (singoli o doppi), esegue alcuni calcoli e produce valori in virgola mobile in output (anche singoli o doppi). Sto lavorando principalmente con MSVC 2008, ma ho anche intenzione di lavorare con MinGW / GCC. Sto programmando in C ++.

Qual è il modo tipico di misurare programmaticamente quanti errori ci sono nei risultati? Supponendo che sia necessario utilizzare una libreria di precisione arbitraria: qual è la migliore libreria di questo tipo se non mi interessa la velocità?

Risposte:


17

Se stai cercando un buon limite per il tuo errore di arrotondamento, non hai necessariamente bisogno di una libreria di precisione aribtrary. È possibile invece utilizzare l'analisi degli errori in esecuzione.

Non sono riuscito a trovare un buon riferimento online, ma è tutto descritto nella Sezione 3.3 del libro di Nick Higham "Precisione e stabilità degli algoritmi numerici". L'idea è abbastanza semplice:

  1. Fattorizza nuovamente il codice in modo da avere un'unica assegnazione di una singola operazione aritmetica su ciascuna riga.
  2. Per ogni variabile, ad esempio x, creare una variabile x_errche viene inizializzata a zero quando xviene assegnata una costante.
  3. Per ogni operazione, ad esempio z = x * y, aggiornare la variabile z_errutilizzando il modello standard di aritmetica in virgola mobile e gli zerrori risultanti e in esecuzione x_erre y_err.
  4. Il valore restituito della tua funzione dovrebbe quindi anche avere un rispettivo _errvalore associato ad essa. Questo è un dato dipendente dai dati sull'errore di arrotondamento totale.

La parte difficile è il passaggio 3. Per le operazioni aritmetiche più semplici, è possibile utilizzare le seguenti regole:

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

dove si u = eps/2trova il roundoff dell'unità. Sì, le regole per +e -sono le stesse. Le regole per qualsiasi altra operazione op(x)possono essere facilmente estratte usando l'espansione della serie Taylor del risultato applicato op(x + x_err). Oppure puoi provare a cercare su Google. O usando il libro di Nick Higham.

Ad esempio, considerare il seguente codice Matlab / Octave che valuta un polinomio nei coefficienti ain un punto xusando lo schema Horner:

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

Per il primo passo, abbiamo suddiviso le due operazioni in 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

Introduciamo quindi le _errvariabili. Si noti che gli input ae xsono considerati esatti, ma potremmo anche richiedere all'utente di passare i valori corrispondenti per a_erre 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

Infine, applichiamo le regole sopra descritte per ottenere i termini di errore:

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

Si noti che poiché non abbiamo a_erro x_err, ad esempio, si presume che siano zero, i rispettivi termini vengono semplicemente ignorati nelle espressioni di errore.

Et voilà! Ora abbiamo uno schema Horner che restituisce una stima dell'errore dipendente dai dati (nota: questo è un limite superiore dell'errore) insieme al risultato.

Come nota a margine, dal momento che stai usando C ++, potresti prendere in considerazione la possibilità di creare la tua classe per i valori a virgola mobile che porta con sé il _errtermine e sovraccaricare tutte le operazioni aritmetiche per aggiornare questi valori come descritto sopra. Per i codici di grandi dimensioni, questo può essere il percorso più semplice, sebbene computazionalmente meno efficiente. Detto questo, potresti essere in grado di trovare una tale classe online. Una rapida ricerca su Google mi ha dato questo link .

±ux(1±u)


1
+1 per questa analisi, perché è interessante. Mi piace il lavoro di Higham. Ciò che mi preoccupa è che richiedere a un utente di scrivere a mano quel codice aggiuntivo (invece che come l'aritmetica di intervallo semi-automatico) potrebbe essere soggetto a errori man mano che il numero di operazioni numeriche aumenta.
Geoff Oxberry,

1
@GeoffOxberry: sono completamente d'accordo con il problema della complessità. Per codici più grandi consiglio vivamente di scrivere una classe / tipo di dati che sovraccarichi le operazioni sui doppi in modo da dover implementare correttamente ogni operazione una sola volta. Sono abbastanza sorpreso che non sembra esserci qualcosa del genere per Matlab / Octave.
Pedro,

Mi piace questa analisi, ma poiché il calcolo dei termini di errore viene eseguito anche in virgola mobile, questi termini di errore non saranno imprecisi a causa di errori in virgola mobile?
Plasmacel,

8

Una libreria portatile e open source per l'aritmetica in virgola mobile di precisione arbitraria (e molto altro) è NTL di Victor Shoup , disponibile in formato sorgente C ++.

A un livello inferiore si trova la GNU Multiple Precision (GMP) Bignum Library , anch'essa un pacchetto open source.

NTL può essere utilizzato con GMP quando sono richieste prestazioni più rapide, ma NTL fornisce le proprie routine di base che sono certamente utilizzabili se "non ti interessa la velocità". GMP afferma di essere la "biblioteca di bignum più veloce". GMP è in gran parte scritto in C, ma ha un'interfaccia C ++.

Aggiunto: Mentre l'aritmetica dell'intervallo può dare limiti superiori e inferiori sulla risposta esatta in modo automatizzato, ciò non misura accuratamente l'errore in un calcolo di precisione "standard" perché la dimensione dell'intervallo aumenta in genere con ogni operazione (in un relativo o senso di errore assoluto).

Il modo tipico di trovare la dimensione dell'errore, per errori di arrotondamento o per errori di discretizzazione, ecc., È calcolare un valore di precisione aggiuntivo e confrontarlo con il valore di precisione "standard". È necessaria solo una modesta precisione aggiuntiva per determinare la dimensione dell'errore stessa con ragionevole accuratezza, poiché i soli errori di arrotondamento sono sostanzialmente maggiori nella precisione "standard" di quanto non lo siano nel calcolo della precisione extra.

Il punto può essere illustrato confrontando i calcoli di precisione singola e doppia. Si noti che in C ++ le espressioni intermedie sono sempre calcolate in (almeno) doppia precisione, quindi se vogliamo illustrare come sarebbe un calcolo in una precisione singola "pura", dobbiamo memorizzare i valori intermedi in un'unica precisione.

Snippet di codice 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;

L'output dall'alto (Cygwin / MinGW32 GCC tool chain):

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

Quindi l'errore riguarda ciò che ci si aspetta nell'arrotondare 1/3 alla precisione singola. Non ci si aspetterebbe (sospetterei) che si ottenga la correzione corretta di più di un paio di cifre decimali , poiché la misurazione dell'errore è per grandezza e non per precisione.


Il tuo approccio è decisamente matematicamente valido. Penso che il compromesso sia rigoroso; le persone che sono pedanti riguardo all'errore indicheranno il rigore dell'aritmetica dell'intervallo, ma sospetto che in molte applicazioni sarebbe sufficiente calcolare con maggiore precisione e che le stime di errore risultanti sarebbero probabilmente più strette, come fai notare.
Geoff Oxberry,

Questo è l'approccio che stavo immaginando che avrei usato. Potrei provare alcune di queste diverse tecniche per vedere quale è più appropriato per la mia applicazione. L'aggiornamento dell'esempio di codice è molto apprezzato!
user_123abc

7

GMP (ovvero la libreria GNU Multiple Precision) è la migliore libreria di precisione arbitraria che io conosca.

Non conosco alcun modo programmatico per misurare l'errore nei risultati di una funzione arbitraria in virgola mobile. Una cosa che potresti provare è calcolare l'estensione di intervallo di una funzione usando l' aritmetica di intervallo . In C ++, dovresti usare una sorta di libreria per calcolare le estensioni degli intervalli; una di queste librerie è la libreria aritmetica Boost Interval. Fondamentalmente, per misurare l'errore, forniresti come argomenti i tuoi intervalli di funzioni che hanno una larghezza di 2 volte il arrotondamento dell'unità (approssimativamente), centrato sui valori di interesse, e quindi il tuo output sarebbe una raccolta di intervalli, la larghezza di che ti darebbe una stima prudente dell'errore. Una difficoltà con questo approccio è che l'aritmetica dell'intervallo usata in questo modo può sovrastimare l'errore di quantità significative, ma questo approccio è il più "programmatico" a cui riesco a pensare.


Ah, ho appena notato l'aritmetica dell'intervallo menzionata nella tua risposta ... Votato!
Ali,

2
Richard Harris ha scritto un'eccellente serie di articoli sulla rivista ACCU Overload sui Floating Point Blues . Il suo articolo sull'aritmetica degli intervalli è in Sovraccarico 103 ( pdf , p19-24).
Mark Booth,

6

L' analisi degli intervalli consente di ottenere una stima degli errori rigorosa e automatica . Lavori con intervalli anziché con numeri. Ad esempio aggiunta:

[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]

L'arrotondamento può anche essere gestito in modo rigoroso, vedere l' aritmetica dell'intervallo arrotondato .

Finché il tuo input è costituito da intervalli ristretti, le stime sono OK e sono poco costose da calcolare. Sfortunatamente, l'errore è spesso sopravvalutato, vedere il problema della dipendenza .

Non conosco alcuna libreria aritmetica ad intervallo di precisione arbitraria.

Dipende dal tuo problema a portata di mano se l'aritmetica dell'intervallo può soddisfare o meno le tue esigenze.


4

La libreria GNU MPFR è una libreria float di precisione arbitraria che ha un'elevata precisione (in particolare, arrotondamento corretto per tutte le operazioni, che non è così facile come sembra) come uno dei loro punti di messa a fuoco principali. Usa GNU MP sotto il cofano. Ha un'estensione chiamata MPFI che esegue l'aritmetica dell'intervallo, che - come suggerisce la risposta di Geoff - potrebbe tornare utile ai fini della verifica: continuare ad aumentare la precisione di lavoro fino a quando l'intervallo risultante non rientra in piccoli limiti.

Questo non funzionerà sempre, comunque; in particolare non è necessariamente efficace se stai facendo qualcosa come l'integrazione numerica, in cui ogni passaggio comporta un "errore" indipendente dai problemi di arrotondamento. In tal caso, prova un pacchetto specializzato come COZY infinity che lo fa molto bene usando algoritmi specifici per limitare l'errore di integrazione (e usando i cosiddetti modelli di Taylor invece degli intervalli).


Sono d'accordo; l'integrazione numerica è sicuramente un caso in cui l'aritmetica a intervallo ingenuo può andare storta. Tuttavia, anche i modelli di Taylor usano l'aritmetica degli intervalli. Conosco il lavoro di Makino e Berz e credo che usino il modello di Taylor nel senso di RE Moore, anche se usano anche trucchi che coinvolgono quella che chiamano "algebra differenziale".
Geoff Oxberry,

@GeoffOxberry: Sì, penso che questa algebra differenziale sia roba da mettere in relazione con l'errore nella fase di integrazione.
Erik P.

0

Mi è stato detto che MPIR è una buona libreria da usare se stai lavorando con Visual Studio:

http://mpir.org/


Benvenuti su SciComp.SE! Potresti aggiungere alcuni dettagli su come questa libreria può essere utilizzata per misurare l'errore dei calcoli in virgola mobile?
Christian Clason,

Cercherò; In realtà non ho ancora installato MPIR sul mio computer! Ho creato GMP e MPFR.
pescatore il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.