Analysieren von numerischen Fehlern in C ++ - Funktionen

20

Angenommen, ich habe eine Funktion, die mehrere Gleitkommawerte (einfach oder doppelt) als Eingabe verwendet, Berechnungen durchführt und Ausgabegleitkommawerte (auch einfach oder doppelt) erzeugt. Ich arbeite hauptsächlich mit MSVC 2008, habe aber auch vor, mit MinGW / GCC zu arbeiten. Ich programmiere in C ++.

Wie lässt sich programmgesteuert messen, wie viel Fehler in den Ergebnissen enthalten ist? Angenommen, ich benötige eine Bibliothek mit beliebiger Genauigkeit: Was ist die beste Bibliothek, wenn mir die Geschwindigkeit egal ist?

user_123abc
quelle

Antworten:

17

Wenn Sie nach einer guten Grenze für Ihren Rundungsfehler suchen, benötigen Sie nicht unbedingt eine Bibliothek mit beliebiger Genauigkeit. Sie können stattdessen die laufende Fehleranalyse verwenden.

Ich konnte keine gute Online-Referenz finden, aber alles ist in Abschnitt 3.3 von Nick Highams Buch "Genauigkeit und Stabilität numerischer Algorithmen" beschrieben. Die Idee ist ganz einfach:

  1. Berechnen Sie Ihren Code neu, sodass Sie in jeder Zeile eine einzelne Zuordnung einer einzelnen arithmetischen Operation haben.
  2. xErstellen Sie z. B. für jede Variable eine Variable, x_errdie auf Null initialisiert wird, wenn xeine Konstante zugewiesen wird.
  3. z = x * yAktualisieren Sie für jede Operation z. B. die Variable z_errmit dem Standardmodell der Gleitkomma-Arithmetik und den daraus resultierenden zund den ausgeführten Fehlern x_errund y_err.
  4. Dem Rückgabewert Ihrer Funktion sollte dann auch ein entsprechender _errWert zugeordnet sein. Dies ist eine datenabhängige Grenze Ihres gesamten Rundungsfehlers.

Der knifflige Teil ist Schritt 3. Für die einfachsten Rechenoperationen können Sie die folgenden Regeln verwenden:

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

wo u = eps/2ist die gerundete Einheit. Ja, die Regeln für +und -sind gleich. op(x)Mit der Taylor-Reihen-Erweiterung des Ergebnisses, auf das angewendet wird, können Regeln für jede andere Operation einfach extrahiert werden op(x + x_err). Oder Sie können versuchen, zu googeln. Oder mit Nick Highams Buch.

Betrachten Sie als Beispiel den folgenden Matlab / Octave-Code, der Polynome in den Koeffizienten aan einem Punkt unter xVerwendung des Horner-Schemas auswertet :

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

Für den ersten Schritt teilen wir die beiden Operationen 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

Wir führen dann die _errVariablen ein. Beachten Sie, dass die Eingaben aund xals genau angenommen werden, aber wir könnten genauso gut auch den Benutzer auffordern, entsprechende Werte für a_errund zu übergeben 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

Schließlich wenden wir die oben beschriebenen Regeln an, um die Fehlerausdrücke zu erhalten:

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

Beachten Sie, dass die entsprechenden Ausdrücke in den Fehlerausdrücken einfach ignoriert werden , da wir keine haben a_erroder x_errz. B. angenommen werden, dass sie Null sind.

Et voilà! Wir haben jetzt ein Horner-Schema, das eine datenabhängige Fehlerschätzung (Anmerkung: Dies ist eine Obergrenze für den Fehler) neben dem Ergebnis zurückgibt .

Als Randnotiz: Da Sie C ++ verwenden, können Sie eine eigene Klasse für Gleitkommawerte erstellen, die den _errTerm enthält, und alle arithmetischen Operationen überladen, um diese Werte wie oben beschrieben zu aktualisieren. Bei großen Codes kann dies die einfachere, wenn auch rechnerisch weniger effiziente Route sein. Allerdings können Sie eine solche Klasse möglicherweise online finden. Eine schnelle Google-Suche gab mir diesen Link .

±ux(1±u)

Pedro
quelle
1
+1 für diese Analyse, weil es interessant ist. Ich mag Highams Arbeit. Was mich beunruhigt, ist, dass es fehleranfällig sein kann, wenn ein Benutzer diesen zusätzlichen Code von Hand schreiben muss (anstelle einer halbautomatischen Intervallarithmetik), wenn die Anzahl der numerischen Operationen groß wird.
Geoff Oxberry
1
@GeoffOxberry: Ich stimme dem Komplexitätsproblem voll und ganz zu. Für größere Codes würde ich dringend empfehlen, eine Klasse / einen Datentyp zu schreiben, die / der Operationen auf Doubles überlädt, so dass jede Operation nur einmal korrekt implementiert werden muss. Ich bin ziemlich überrascht, dass es für Matlab / Octave nicht so etwas zu geben scheint.
Pedro
Ich mag diese Analyse, aber da die Berechnung der Fehlerausdrücke auch in Gleitkommazahlen durchgeführt wird, sind diese Fehlerausdrücke aufgrund von Gleitkommazahlen nicht ungenau.
Plasmacel
8

Eine schöne portable und Open-Source-Bibliothek für Gleitkomma-Arithmetik mit beliebiger Genauigkeit (und vieles mehr) ist Victor Shoups NTL , die in C ++ - Quellform verfügbar ist.

Auf einer niedrigeren Ebene befindet sich die GNU Multiple Precision (GMP) Bignum Library , ebenfalls ein Open-Source-Paket.

NTL kann mit GMP verwendet werden, wenn eine schnellere Leistung erforderlich ist. NTL stellt jedoch eigene Basisroutinen zur Verfügung, die auf jeden Fall verwendet werden können, wenn Sie sich nicht für Geschwindigkeit interessieren. GMP behauptet, die "schnellste Bignumbibliothek" zu sein. GMP ist größtenteils in C geschrieben, verfügt jedoch über eine C ++ - Schnittstelle.

Hinzugefügt: Während Intervallarithmetik auf automatisierte Weise obere und untere Grenzen für die exakte Antwort festlegen kann, misst dies den Fehler bei einer "Standard" -Präzisionsberechnung nicht genau, da die Intervallgröße normalerweise mit jeder Operation wächst (entweder relativ oder relativ) absoluter Fehlersinn).

Die typische Methode zum Ermitteln der Fehlergröße, entweder für Rundungsfehler oder für Diskretisierungsfehler usw., besteht darin, einen zusätzlichen Präzisionswert zu berechnen und diesen mit dem "Standard" -Präzisionswert zu vergleichen. Es ist nur eine bescheidene zusätzliche Genauigkeit erforderlich, um die Fehlergröße selbst mit angemessener Genauigkeit zu bestimmen, da die Rundungsfehler allein in der "Standard" -genauigkeit wesentlich größer sind als in der Berechnung mit zusätzlicher Genauigkeit.

Der Punkt kann durch Vergleichen von Berechnungen mit einfacher und doppelter Genauigkeit veranschaulicht werden. Beachten Sie, dass in C ++ Zwischenausdrücke immer mit (mindestens) doppelter Genauigkeit berechnet werden. Wenn Sie also veranschaulichen möchten, wie eine Berechnung mit "reiner" einfacher Genauigkeit aussehen würde, müssen Sie die Zwischenwerte mit einfacher Genauigkeit speichern.

C-Code-Snippet

    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;

Die Ausgabe von oben (Cygwin / MinGW32 GCC-Toolkette):

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

Der Fehler ist also ungefähr das, was man erwartet, wenn man 1/3 auf einfache Genauigkeit rundet. Es würde einem (ich vermute) nichts ausmachen , mehr als ein paar Nachkommastellen im Fehler zu korrigieren, da die Messung des Fehlers nach der Größe und nicht nach der Genauigkeit erfolgt.

Hardmath
quelle
Ihr Ansatz ist auf jeden Fall mathematisch fundiert. Ich denke, der Kompromiss ist streng. Leute, die pedantisch in Bezug auf Fehler sind, werden auf die Strenge der Intervallarithmetik hinweisen, aber ich vermute, dass in vielen Anwendungen eine genauere Berechnung ausreichen würde und die resultierenden Fehlerschätzungen wahrscheinlich strenger ausfallen würden, wie Sie betonen.
Geoff Oxberry
Dies ist der Ansatz, den ich mir vorgestellt habe. Ich könnte einige dieser verschiedenen Techniken ausprobieren, um herauszufinden, welche für meine Anwendung am besten geeignet sind. Das Codebeispiel-Update wird sehr geschätzt!
user_123abc
7

GMP (dh die GNU Multiple Precision Library) ist die beste beliebige Präzisionsbibliothek, die ich kenne.

Ich kenne keine programmatische Methode, um den Fehler in den Ergebnissen einer beliebigen Gleitkommafunktion zu messen. Sie könnten versuchen, eine Intervallverlängerung einer Funktion mit Intervallarithmetik zu berechnen . In C ++ müssten Sie eine Bibliothek verwenden, um die Intervallerweiterungen zu berechnen. Eine solche Bibliothek ist die Boost Interval Arithmetic Library. Um den Fehler zu messen, würden Sie als Argumente für Ihre Funktionsintervalle angeben, die eine Breite von 2-facher Rundung (ungefähr) haben, zentriert auf die interessierenden Werte, und dann wäre Ihre Ausgabe eine Sammlung von Intervallen, die Breite von das würde Ihnen eine konservative Schätzung des Fehlers geben. Eine Schwierigkeit bei diesem Ansatz besteht darin, dass die auf diese Weise verwendete Intervallarithmetik Fehler erheblich überschätzen kann, aber dieser Ansatz ist der "programmatischste", den ich mir vorstellen kann.

Geoff Oxberry
quelle
Ah, ich habe gerade die in Ihrer Antwort erwähnte Intervallarithmetik bemerkt ... Upvoted!
Ali
2
Richard Harris hat eine exzellente Artikelserie im ACCU- Journal Overload über den Floating Point Blues geschrieben . Sein Artikel über Intervallarithmetik ist in Overload 103 ( pdf , S. 19-24).
Mark Booth
6

Eine rigorose und automatische Fehlerabschätzung kann durch Intervallanalyse erreicht werden . Sie arbeiten mit Intervallen anstelle von Zahlen. Zum Beispiel Zugabe:

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

Rundung kann auch rigoros gehandhabt werden, siehe Abgerundete Intervallarithmetik .

Solange Ihre Eingabe aus engen Intervallen besteht, sind die Schätzungen in Ordnung und können nicht billig berechnet werden. Leider wird der Fehler oft überschätzt, siehe das Abhängigkeitsproblem .

Ich kenne keine Arithmetikbibliothek für beliebige Präzisionsintervalle.

Es hängt von Ihrem Problem ab, ob die Intervallarithmetik Ihren Anforderungen entspricht oder nicht.

Ali
quelle
4

Die GNU MPFR-Bibliothek ist eine Float-Bibliothek mit beliebiger Genauigkeit, die eine hohe Genauigkeit (insbesondere korrekte Rundung für alle Operationen, was nicht so einfach ist, wie es sich anhört) als einen ihrer Hauptschwerpunkte aufweist. Es verwendet GNU MP unter der Haube. Es hat eine Erweiterung namens MPFI , die Intervall-Arithmetik ausführt , was - wie Geoffs Antwort nahelegt - für Überprüfungszwecke nützlich sein könnte: Erhöhen Sie die Arbeitsgenauigkeit so lange, bis das resultierende Intervall innerhalb kleiner Grenzen liegt.

Dies wird jedoch nicht immer funktionieren. Insbesondere ist es nicht unbedingt effektiv, wenn Sie so etwas wie numerische Integration ausführen, bei der jeder Schritt einen "Fehler" enthält, der von Rundungsproblemen unabhängig ist. Versuchen Sie in diesem Fall ein spezielles Paket wie COSY infinity, das dies sehr gut mit spezifischen Algorithmen zur Begrenzung des Integrationsfehlers erledigt (und sogenannte Taylor-Modelle anstelle von Intervallen verwendet).

Erik P.
quelle
Genau; Die numerische Integration ist definitiv ein Fall, in dem naive Intervallarithmetik schief gehen kann. Sogar Taylor-Modelle verwenden jedoch Intervallarithmetik. Ich kenne die Arbeit von Makino und Berz und ich glaube, dass sie das Taylor-Modell im Sinne von RE Moore verwenden, obwohl sie auch Tricks anwenden, die die sogenannte "Differentialalgebra" betreffen.
Geoff Oxberry
@GeoffOxberry: Ja - ich denke, diese Differentialalgebra ist etwas, um den Fehler im Integrationsschritt einzugrenzen.
Erik P.
0

Mir wurde gesagt, dass MPIR eine gute Bibliothek ist, wenn Sie mit Visual Studio arbeiten:

http://mpir.org/

Fischerhut
quelle
Willkommen bei SciComp.SE! Können Sie einige Details hinzufügen, wie diese Bibliothek zum Messen des Fehlers von Gleitkommaberechnungen verwendet werden kann?
Christian Clason
Ich werde es versuchen; Ich habe MPIR noch nicht auf meinem Computer eingerichtet! Ich habe GMP und MPFR eingerichtet.
Fishermanhat