it-swarm.com.de

Vergleich der Fließkommazahl mit Null

Die C++ - FAQ -Lite "[29.17] Warum funktioniert mein Fließkommabgleich nicht?" empfiehlt diesen Gleichheitstest:

#include <cmath>  /* for std::abs(double) */

inline bool isEqual(double x, double y)
{
  const double epsilon = /* some small number such as 1e-5 */;
  return std::abs(x - y) <= epsilon * std::abs(x);
  // see Knuth section 4.2.2 pages 217-218
}
  1. Ist es richtig, dass dies bedeutet, dass die einzigen Zahlen, die gleich Null sind, +0 und -0 sind?
  2. Sollte man diese Funktion auch beim Testen auf Null oder eher einem Test wie |x| < epsilon verwenden?

Update

Wie von Daniel Daranas ausgeführt, sollte die Funktion wahrscheinlich besser als isNearlyEqual bezeichnet werden (was für mich der Fall ist).

Jemand hat diesen Link aufgezeigt, den ich prominenter mit anderen teilen möchte.

45

Sie haben mit Ihrer Beobachtung recht. 

Wenn x == 0.0, dann ist abs(x) * epsilon Null und Sie testen, ob abs(y) <= 0.0.

Wenn y == 0.0, dann testen Sie abs(x) <= abs(x) * epsilon, was entweder epsilon >= 1 (es ist nicht) oder x == 0.0 bedeutet.

Entweder wäre is_equal(val, 0.0) oder is_equal(0.0, val) sinnlos, und Sie könnten einfach val == 0.0 sagen. Wenn Sie nur genau+0.0 und -0.0 akzeptieren möchten.

Die FAQ-Empfehlung ist in diesem Fall von begrenztem Nutzen. Es gibt keinen Fließkomma-Vergleich "Einheitsgröße". Sie müssen über die Semantik Ihrer Variablen, den akzeptablen Wertebereich und die Fehlergröße nachdenken, die durch Ihre Berechnungen eingeführt wurden. Sogar in der FAQ wird eine Einschränkung erwähnt, die besagt, dass diese Funktion normalerweise kein Problem ist, "wenn die Größen von x und y deutlich größer sind als Epsilon, aber Ihre Laufleistung kann variieren".

35
Ben Voigt

Nein.

Gleichheit ist Gleichheit.

Die Funktion, die Sie geschrieben haben, testet nicht zwei Doubles auf Gleichheit, wie der Name verspricht. Es wird nur getestet, ob zwei Doubles nahe genug sind.

Wenn Sie wirklich zwei Gleiche auf Gleichheit testen möchten, verwenden Sie diesen:

inline bool isEqual(double x, double y)
{
   return x == y;
}

Codierungsstandards empfehlen in der Regel einen Vergleich von zwei Doubles hinsichtlich der exakten Gleichheit. Das ist aber ein anderes Thema. Wenn Sie tatsächlich zwei Doubles auf exakte Gleichheit vergleichen möchten, ist x == y der gewünschte Code.

10.00000000000000001 ist nicht gleich 10.0, egal, was sie Ihnen sagen.

Ein Beispiel der Verwendung der exakten Gleichheit liegt vor, wenn ein bestimmter Wert eines Doppels als Synonym für einen bestimmten Zustand verwendet wird, beispielsweise "anstehende Berechnung" oder "keine Daten verfügbar". Dies ist nur möglich, wenn die tatsächlichen numerischen Werte nach dieser ausstehenden Berechnung nur eine Teilmenge der möglichen Werte eines Doppels sind. Der typischste Fall ist, wenn dieser Wert nicht negativ ist und Sie -1.0 als (genaue) Darstellung einer "ausstehenden Berechnung" oder "keine Daten verfügbar" verwenden. Das könnte man mit einer Konstanten darstellen:

const double NO_DATA = -1.0;

double myData = getSomeDataWhichIsAlwaysNonNegative(someParameters);

if (myData != NO_DATA)
{
    ...
}
17
Daniel Daranas

Sie können std::nextafter mit einer festen factor der epsilon eines Wertes wie dem folgenden verwenden:

bool isNearlyEqual(double a, double b)
{
  int factor = /* a fixed factor of epsilon */;

  double min_a = a - (a - std::nextafter(a, std::numeric_limits<double>::lowest())) * factor;
  double max_a = a + (std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor;

  return min_a <= b && max_a >= b;
}
6
Daniel Laügt

Wenn Sie nur an +0.0 Und -0.0 Interessiert sind, können Sie fpclassify aus <cmath> Verwenden. Zum Beispiel:

if( FP_ZERO == fpclassify(x) ) do_something;

5
DaBler

2 + 2 = 5 (*)

(für einige Gleitkommawerte von 2)

Dieses Problem tritt häufig auf, wenn wir von "Fließkomma" als Möglichkeit zur Erhöhung der Genauigkeit denken. Dann führen wir den "schwebenden" Teil durch, was bedeutet, dass welche Zahlen nicht dargestellt werden können.

Während wir vielleicht leicht in der Lage sind, "1.0, -1.0, 0.1, -0.1" darzustellen, wenn wir zu größeren Zahlen kommen, sehen wir Näherungen - oder sollten, sollten wir sie jedoch ausblenden, indem wir die Zahlen für die Anzeige abschneiden.

Wir denken daher, dass der Computer "0.003" speichert, stattdessen jedoch "0.0033333333334".

Was passiert, wenn Sie "0.0003 - 0.0002" ausführen? Wir erwarten, dass .0001, aber die tatsächlichen Werte, die gespeichert werden, könnten eher "0.00033" - "0.00029" sein, was "0.000004", oder den nächstliegenden darstellbaren Wert ergibt, der 0 sein kann, oder "0.000006". .

Bei aktuellen Gleitkommaoperationen gilt es ist nicht garantiert, dass (a/b) * b == a .

#include <stdio.h>

// defeat inline optimizations of 'a / b * b' to 'a'
extern double bodge(int base, int divisor) {
    return static_cast<double>(base) / static_cast<double>(divisor);
}

int main() {
    int errors = 0;
    for (int b = 1; b < 100; ++b) {
        for (int d = 1; d < 100; ++d) {
            // b / d * d ... should == b
            double res = bodge(b, d) * static_cast<double>(d);
            // but it doesn't always
            if (res != static_cast<double>(b))
                ++errors;
        }
    }
    printf("errors: %d\n", errors);
}

ideone berichtet über 599 Fälle, in denen (b * d)/d! = b nur die 10.000 Kombinationen von 1 <= b <= 100 und 1 <= d <= 100 verwendet.

Die in FAQ beschriebene Lösung besteht im Wesentlichen darin, eine Granularitätsbedingung anzuwenden - um if (a == b +/- epsilon) zu testen.

Ein alternativer Ansatz besteht darin, das Problem vollständig zu vermeiden, indem Sie die Fixpunktgenauigkeit verwenden oder Ihre gewünschte Granularität als Basiseinheit für Ihre Lagerung verwenden. Z.B. Wenn Sie Zeiten im Nanosekundenbereich speichern möchten, verwenden Sie Nanosekunden als Speichereinheit.

In C++ 11 wurde std :: ratio als Basis für Festkomma-Konvertierungen zwischen verschiedenen Zeiteinheiten eingeführt.

4
kfsone

Wie @Exceptyon ausgeführt hat, ist diese Funktion relativ zu den Werten, die Sie vergleichen. Der Epsilon * abs(x)-Maßstab wird basierend auf dem Wert von x skaliert, sodass Sie ein Vergleichsergebnis erhalten, das genau wie epsilon ist, unabhängig vom Wertebereich in x oder y. 

Wenn Sie Null (y) mit einem anderen wirklich kleinen Wert (x) vergleichen, beispielsweise 1e-8, ist abs(x-y) = 1e-8 immer noch viel größer als epsilon *abs(x) = 1e-13. Wenn Sie also nicht mit einer extrem kleinen Zahl zu tun haben, die nicht in einem Doppeltyp dargestellt werden kann, sollte diese Funktion den Job ausführen und wird nur mit +0 und -0 mit Null abgeglichen.

Die Funktion scheint für den Nullvergleich vollkommen gültig zu sein. Wenn Sie vorhaben, es zu verwenden, schlage ich vor, dass Sie es überall verwenden, wo Floats beteiligt sind, und keine speziellen Fälle für Dinge wie Null haben, nur damit der Code einheitlich ist.

ps: Dies ist eine nette Funktion. Danke, dass Sie darauf hingewiesen haben.

1
Arun R

Der einfache Vergleich von FP - Zahlen hat seine eigenen Besonderheiten und der Schlüssel ist das Verständnis des FP -Formats (siehe https://en.wikipedia.org/wiki/IEEE_floating_point ).

Wenn FP Zahlen auf unterschiedliche Weise berechnet werden, eine durch sin (), andere durch exp (), funktioniert strikte Gleichheit nicht, obwohl mathematisch Zahlen gleich sein könnten. Der gleiche Weg wird nicht mit der Konstanten gleich funktionieren. Tatsächlich dürfen FP Zahlen nicht mit strikter Gleichheit (==) verglichen werden.

In solchen Fällen sollte die DBL_EPSIPON-Konstante verwendet werden. Dies ist der minimale Wert change. Die Darstellung von 1,0 wird nicht größer als 1,0. Für Gleitkommazahlen, die nicht größer als 2.0 DBL_EPSIPON sind, gibt es überhaupt keine. Inzwischen hat DBL_EPSILON den Exponenten -16, was bedeutet, dass alle Zahlen, beispielsweise mit dem Exponenten -34, im Vergleich zu DBL_EPSILON absolut gleich wären.

Siehe auch Beispiel , warum 10.0 = 10.0000000000000001

Der Vergleich von zwei Fließkommazahlen hängt von diesen Zahlen ab. Wir sollten DBL_EPSILON für sie berechnen, die für den Vergleich sinnvoll sind. Wir sollten einfach DBL_EPSILON mit einer dieser Zahlen multiplizieren. Welcher von denen? Maximum natürlich

bool close_enough(double a, double b){
    if (fabs(a - b) <= DBL_EPSILON * std::fmax(fabs(a), fabs(b)))
    {
        return true;
    }
    return false;
}

Alle anderen Möglichkeiten würden zu Fehlern mit Ungleichheit führen, die sehr schwer zu fassen wären

1

beachten Sie, dass der Code lautet:

std::abs((x - y)/x) <= epsilon

sie verlangen, dass der "relative Fehler" in der Variable <= epsilon ist, nicht der absolute Unterschied

0
Exceptyon