671 Stimmen

Welches ist die effektivste Methode für einen Float- und Double-Vergleich?

Was wäre der effizienteste Weg, um zwei double oder zwei float Werte?

Dies einfach zu tun, ist nicht korrekt:

bool CompareDoubles1 (double A, double B)
{
   return A == B;
}

Aber so etwas wie:

bool CompareDoubles2 (double A, double B) 
{
   diff = A - B;
   return (diff < EPSILON) && (-diff < EPSILON);
}

Das scheint eine Verschwendung von Bearbeitungszeit zu sein.

Kennt jemand einen intelligenteren Float-Vergleicher?

40voto

Shafik Yaghmour Punkte 147749

Realizing dies ist ein altes Thema, aber dieser Artikel ist eine der am meisten geradeaus diejenigen, die ich gefunden habe auf den Vergleich von Fließkommazahlen und wenn Sie wollen, um mehr zu erforschen, es hat mehr detaillierte Referenzen als gut und es die wichtigsten Website deckt eine vollständige Palette von Fragen rund um Fließkommazahlen Der Fließkomma-Leitfaden :Vergleich .

Einen etwas praktischeren Artikel finden wir in Fließkomma-Toleranzen neu überdacht und stellt fest, dass es absolute Toleranz Test, der in C++ auf Folgendes hinausläuft:

bool absoluteToleranceCompare(double x, double y)
{
    return std::fabs(x - y) <= std::numeric_limits<double>::epsilon() ;
}

und relative Toleranz Test:

bool relativeToleranceCompare(double x, double y)
{
    double maxXY = std::max( std::fabs(x) , std::fabs(y) ) ;
    return std::fabs(x - y) <= std::numeric_limits<double>::epsilon()*maxXY ;
}

Der Artikel stellt fest, dass der absolute Test fehlschlägt, wenn x y y groß sind, und versagt im relativen Fall, wenn sie klein sind. Unter der Annahme, dass die absolute und relative Toleranz gleich ist, würde ein kombinierter Test wie folgt aussehen:

bool combinedToleranceCompare(double x, double y)
{
    double maxXYOne = std::max( { 1.0, std::fabs(x) , std::fabs(y) } ) ;

    return std::fabs(x - y) <= std::numeric_limits<double>::epsilon()*maxXYOne ;
}

28voto

Chris de Vries Punkte 55383

Der portable Weg, um epsilon in C++ zu erhalten, ist

#include <limits>
std::numeric_limits<double>::epsilon()

Die Vergleichsfunktion lautet dann

#include <cmath>
#include <limits>

bool AreSame(double a, double b) {
    return std::fabs(a - b) < std::numeric_limits<double>::epsilon();
}

27voto

Shital Shah Punkte 54846

Ich habe eine ganze Weile damit verbracht, das Material in diesem großartigen Thread durchzugehen. Ich bezweifle, dass jeder so viel Zeit damit verbringen möchte, also würde ich die Zusammenfassung dessen, was ich gelernt habe, und die Lösung, die ich implementiert habe, hervorheben.

Kurze Zusammenfassung

  1. Ist 1e-8 ungefähr dasselbe wie 1e-16? Wenn Sie verrauschte Sensordaten betrachten, dann wahrscheinlich ja, aber wenn Sie eine Molekularsimulation durchführen, dann vielleicht nicht! Unterm Strich: Sie müssen immer an Folgendes denken Toleranz Wert im Kontext eines bestimmten Funktionsaufrufs und nicht nur als generische app-weite, fest kodierte Konstante.
  2. Für allgemeine Bibliotheksfunktionen ist es immer noch schön, Parameter mit Standardtoleranz . Eine typische Wahl ist numeric_limits::epsilon() was dasselbe ist wie FLT_EPSILON in float.h. Dies ist jedoch problematisch, da epsilon für den Vergleich von Werten wie 1.0 nicht dasselbe ist wie epsilon für Werte wie 1E9. Der FLT_EPSILON ist für 1,0 definiert.
  3. Die offensichtliche Implementierung zur Prüfung, ob die Zahl innerhalb der Toleranz liegt, ist fabs(a-b) <= epsilon Dies funktioniert jedoch nicht, da epsilon standardmäßig für 1,0 definiert ist. Wir müssen epsilon in Bezug auf a und b nach oben oder unten skalieren.
  4. Es gibt zwei Lösungen für dieses Problem: Entweder Sie setzen epsilon proportional zu max(a,b) oder du kannst die nächsten darstellbaren Zahlen um a herum ermitteln und dann sehen, ob b in diesen Bereich fällt. Die erste Methode wird als "relative" Methode bezeichnet, die zweite als ULP-Methode.
  5. Beide Methoden scheitern ohnehin, wenn sie mit 0 verglichen werden. In diesem Fall muss die Anwendung die richtige Toleranz liefern.

Implementierung von Dienstprogrammfunktionen (C++11)

//implements relative method - do not use for comparing with zero
//use this most of the time, tolerance needs to be meaningful in your context
template<typename TReal>
static bool isApproximatelyEqual(TReal a, TReal b, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
    TReal diff = std::fabs(a - b);
    if (diff <= tolerance)
        return true;

    if (diff < std::fmax(std::fabs(a), std::fabs(b)) * tolerance)
        return true;

    return false;
}

//supply tolerance that is meaningful in your context
//for example, default tolerance may not work if you are comparing double with float
template<typename TReal>
static bool isApproximatelyZero(TReal a, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
    if (std::fabs(a) <= tolerance)
        return true;
    return false;
}

//use this when you want to be on safe side
//for example, don't start rover unless signal is above 1
template<typename TReal>
static bool isDefinitelyLessThan(TReal a, TReal b, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
    TReal diff = a - b;
    if (diff < tolerance)
        return true;

    if (diff < std::fmax(std::fabs(a), std::fabs(b)) * tolerance)
        return true;

    return false;
}
template<typename TReal>
static bool isDefinitelyGreaterThan(TReal a, TReal b, TReal tolerance = std::numeric_limits<TReal>::epsilon())
{
    TReal diff = a - b;
    if (diff > tolerance)
        return true;

    if (diff > std::fmax(std::fabs(a), std::fabs(b)) * tolerance)
        return true;

    return false;
}

//implements ULP method
//use this when you are only concerned about floating point precision issue
//for example, if you want to see if a is 1.0 by checking if its within
//10 closest representable floating point numbers around 1.0.
template<typename TReal>
static bool isWithinPrecisionInterval(TReal a, TReal b, unsigned int interval_size = 1)
{
    TReal min_a = a - (a - std::nextafter(a, std::numeric_limits<TReal>::lowest())) * interval_size;
    TReal max_a = a + (std::nextafter(a, std::numeric_limits<TReal>::max()) - a) * interval_size;

    return min_a <= b && max_a >= b;
}

15voto

fulmicoton Punkte 14574

Der von Ihnen geschriebene Code ist fehlerhaft:

return (diff < EPSILON) && (-diff > EPSILON);

Der korrekte Code würde lauten:

return (diff < EPSILON) && (diff > -EPSILON);

(...und ja, das ist etwas anderes)

Ich frage mich, ob man durch die Fabs nicht in manchen Fällen eine faule Bewertung verliert. Ich würde sagen, das hängt vom Compiler ab. Sie sollten vielleicht beide ausprobieren. Wenn sie im Durchschnitt gleichwertig sind, nehmen Sie die Implementierung mit fabs.

Wenn Sie wissen, welcher der beiden Floats mit größerer Wahrscheinlichkeit größer ist als der andere, können Sie die Reihenfolge des Vergleichs beeinflussen, um die träge Auswertung besser zu nutzen.

Schließlich können Sie durch Inlining dieser Funktion ein besseres Ergebnis erzielen. Wahrscheinlich wird das aber nicht viel bringen...

Edit: OJ, danke für die Korrektur deines Codes. Ich habe meinen Kommentar entsprechend gelöscht

14voto

`return fabs(a - b) < EPSILON;

Das ist in Ordnung, wenn:

  • die Größenordnung Ihrer Eingaben ändert sich nicht wesentlich
  • sehr kleine Zahlen mit entgegengesetzten Vorzeichen können als gleich behandelt werden

Aber andernfalls wird es zu Problemen führen. Doppelt genaue Zahlen haben eine Auflösung von etwa 16 Dezimalstellen. Wenn die beiden Zahlen, die Sie vergleichen, größer sind als EPSILON*1.0E16, dann könnten Sie genauso gut sagen:

return a==b;

Ich werde einen anderen Ansatz untersuchen, bei dem davon ausgegangen wird, dass Sie sich um das erste Problem kümmern müssen und dass das zweite Problem für Ihre Anwendung in Ordnung ist. Eine Lösung wäre etwa so:

#define VERYSMALL  (1.0E-150)
#define EPSILON    (1.0E-8)
bool AreSame(double a, double b)
{
    double absDiff = fabs(a - b);
    if (absDiff < VERYSMALL)
    {
        return true;
    }

    double maxAbs  = max(fabs(a) - fabs(b));
    return (absDiff/maxAbs) < EPSILON;
}

Das ist zwar rechenintensiv, aber manchmal ist es genau das Richtige. In meinem Unternehmen müssen wir so vorgehen, weil wir mit einer technischen Bibliothek arbeiten und die Eingaben um einige Dutzend Größenordnungen variieren können.

Wie auch immer, der Punkt ist folgender (und gilt für praktisch jedes Programmierproblem): Überlegen Sie, was Sie brauchen, und entwickeln Sie dann eine Lösung, die Ihren Bedürfnissen entspricht - gehen Sie nicht davon aus, dass die einfache Lösung Ihren Bedürfnissen entspricht. Wenn Sie nach Ihrer Evaluierung feststellen, dass fabs(a-b) < EPSILON reicht aus, perfekt - nutzen Sie es! Aber seien Sie sich seiner Unzulänglichkeiten und anderer möglicher Lösungen bewusst.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X