Was ist der effektivste Weg für Gleitkomma- und Doppelvergleiche?

Lesezeit: 12 Minuten

Was ist der effektivste Weg fur Gleitkomma und Doppelvergleiche
Alex

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

Einfach so zu machen ist nicht richtig:

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

Aber sowas wie:

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

Scheint die Verarbeitung zu verschwenden.

Kennt jemand einen intelligenteren Float-Vergleich?

  • > wäre es effizienter, … am Anfang der Funktion hinzuzufügen? <invoke Knuth>Vorzeitige Optimierung ist die Wurzel allen Übels.</invoke Knuth> Gehen Sie einfach mit abs(ab) < EPS wie oben erwähnt, es ist klar und leicht zu verstehen.

    – Andrew Coleson

    20. August 2008 um 5:55 Uhr

  • Hier ist es in der Boost Test Library implementiert: http://www.boost.org/doc/libs/1_36_0/libs/test/doc/html/utf/testing-tools/floating_point_comparison.html

    – Alessandro Jacobson

    31. Oktober 2008 um 13:38 Uhr

  • Das einzige, was an der Implementierung des ursprünglichen Posters nicht optimal ist, ist, dass es einen zusätzlichen Zweig bei && enthält. Die Antwort von OJ ist optimal. fabs ist eine intrinsische, die eine einzelne Anweisung auf x87 ist, und ich nehme an, auf fast allem anderen auch. Akzeptiere die Antwort von OJ bereits!

    – 3yE

    27. März 2010 um 17:04 Uhr

  • Wenn Sie können, lassen Sie das Gleitkomma weg und verwenden Sie Festkommazahlen. Verwenden Sie beispielsweise {Festkomma} Millimeter statt {Fließkomma} Meter.

    – Thomas Matthäus

    11. Juni 2012 um 2:11 Uhr

  • „Einfach so machen ist nicht richtig“ – Das ist natürlich nur Müll == kann vollkommen richtig sein, aber das hängt ganz vom Kontext ab, der in der Frage nicht angegeben ist. Bis dieser Kontext bekannt ist, == bleibt immer noch die “effizienteste Weg”.

    – Christian Rau

    13. Mai 2013 um 7:39 Uhr


1647291613 527 Was ist der effektivste Weg fur Gleitkomma und Doppelvergleiche
ABl.

Der Vergleich mit einem Epsilon-Wert ist das, was die meisten Leute tun (sogar in der Spieleprogrammierung).

Sie sollten Ihre Implementierung jedoch ein wenig ändern:

bool AreSame(double a, double b)
{
    return fabs(a - b) < EPSILON;
}

Bearbeiten: Christer hat einen Stapel großartiger Informationen zu diesem Thema auf a hinzugefügt letzten Blogbeitrag. Genießen.

  • @DonReba: Nur wenn EPSILON ist definiert als DBL_EPSILON. Normalerweise wird es ein spezifischer Wert sein, der abhängig von der erforderlichen Genauigkeit des Vergleichs gewählt wird.

    – Nemo157

    20. Dezember 2011 um 20:38 Uhr

  • EPSILON Der Vergleich funktioniert nicht, wenn die Floats groß sind, da der Unterschied zwischen aufeinanderfolgenden Floats ebenfalls groß wird. Sehen Dieser Beitrag.

    – kevintodisco

    23. Oktober 2013 um 4:04 Uhr


  • Kein Wunder, dass es in einigen Spielen zu Z-Fighting kommt, wenn weit entfernte Texturen/Objekte flackern, wie in Battlefield 4. Vergleicht man den Unterschied mit EPSILON ist ziemlich nutzlos. Sie müssen mit einem Schwellenwert vergleichen, der für die vorliegenden Einheiten sinnvoll ist. Benutze auch std::abs da es für verschiedene Fließkommatypen überladen ist.

    – Maxim Egorushkin

    19. Februar 2014 um 16:40 Uhr


  • Ich habe abgelehnt, da der Beispielcode den typischen Fehler zeigt, der von der Mehrheit der Programmierer wiederholt wird. Bei Fließkomma geht es immer um relative Fehler, da es sich um Fließkomma (nicht Festkomma) handelt. Es wird also niemals mit einem festen Fehler (Epsilon) korrekt funktionieren.

    – Benutzer2261015

    13. April 2017 um 7:58 Uhr

  • @SirGuy, bitte poste eine Antwort, um zu demonstrieren, wie man das dann richtig macht, oder verlinke hier auf eine. Ich würde gerne Alternativen ohne Epsilon sehen.

    – Gabriel Staples

    26. November 2020 um 2:36 Uhr

  • fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); hat mir das Leben gerettet. LOL Beachten Sie, dass diese Version (ich habe nicht überprüft, ob dies auch für die anderen gilt) auch die Änderung berücksichtigt, die im integralen Teil der Gleitkommazahl auftreten kann (Beispiel: 2147352577.9999997616 == 2147352576.0000000000 wo Sie deutlich sehen können, dass es fast einen Unterschied von gibt 2 zwischen den beiden Zahlen), was ganz nett ist! Dies geschieht, wenn der kumulierte Rundungsfehler den Dezimalteil der Zahl überschreitet.

    – rbaleksandar

    12. Oktober 2016 um 12:24 Uhr


  • Sehr schöner und hilfreicher Artikel von Bruce Dawson, danke!

    – BobMorane

    28. August 2018 um 19:14 Uhr

  • Da diese Frage mit C ++ gekennzeichnet ist, sind Ihre Überprüfungen leichter zu lesen, wenn sie als geschrieben werden std::max(std::abs(a), std::abs(b)) (oder mit std::min()); std::abs in C++ ist mit Float- und Double-Typen überladen, also funktioniert es gut (Sie können immer behalten fabs wegen der Lesbarkeit).

    – Razakhel

    1. Oktober 2018 um 17:09 Uhr


  • definitelyGreaterThan berichtet wahr für etwas, das auf jeden Fall gleich sein sollte, dh nicht größer als.

    – mwpowellhtx

    19. Januar 2019 um 21:05 Uhr

  • Es stellte sich heraus, dass das Problem in meinem Code lag, der Unterschied zwischen dem ursprünglich erwarteten Wert und der analysierten Zeichenfolge.

    – mwpowellhtx

    20. Januar 2019 um 17:31 Uhr

Was ist der effektivste Weg fur Gleitkomma und Doppelvergleiche
krabbel

Das fand ich Google C++-Testframework enthält eine nette plattformübergreifende vorlagenbasierte Implementierung von AlmostEqual2sComplement, die sowohl mit Doubles als auch mit Floats funktioniert. Da es unter der BSD-Lizenz veröffentlicht wird, sollte es kein Problem sein, es in Ihrem eigenen Code zu verwenden, solange Sie die Lizenz behalten. Ich habe den folgenden Code aus extrahiert http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.h https://github.com/google/googletest/blob/master/googletest/include/gtest/internal/gtest-internal.h und fügte die Lizenz oben hinzu.

Stellen Sie sicher, dass Sie GTEST_OS_WINDOWS auf einen Wert #definieren (oder den Code, in dem es verwendet wird, in etwas ändern, das zu Ihrer Codebasis passt – es ist immerhin BSD-lizenziert).

Anwendungsbeispiel:

double left  = // something
double right = // something
const FloatingPoint<double> lhs(left), rhs(right);

if (lhs.AlmostEquals(rhs)) {
  //they're equal!
}

Hier ist der Code:

// Copyright 2005, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Authors: [email protected] (Zhanyong Wan), [email protected] (Sean Mcafee)
//
// The Google C++ Testing Framework (Google Test)


// This template class serves as a compile-time function from size to
// type.  It maps a size in bytes to a primitive type with that
// size. e.g.
//
//   TypeWithSize<4>::UInt
//
// is typedef-ed to be unsigned int (unsigned integer made up of 4
// bytes).
//
// Such functionality should belong to STL, but I cannot find it
// there.
//
// Google Test uses this class in the implementation of floating-point
// comparison.
//
// For now it only handles UInt (unsigned int) as that's all Google Test
// needs.  Other types can be easily added in the future if need
// arises.
template <size_t size>
class TypeWithSize {
 public:
  // This prevents the user from using TypeWithSize<N> with incorrect
  // values of N.
  typedef void UInt;
};

// The specialization for size 4.
template <>
class TypeWithSize<4> {
 public:
  // unsigned int has size 4 in both gcc and MSVC.
  //
  // As base/basictypes.h doesn't compile on Windows, we cannot use
  // uint32, uint64, and etc here.
  typedef int Int;
  typedef unsigned int UInt;
};

// The specialization for size 8.
template <>
class TypeWithSize<8> {
 public:
#if GTEST_OS_WINDOWS
  typedef __int64 Int;
  typedef unsigned __int64 UInt;
#else
  typedef long long Int;  // NOLINT
  typedef unsigned long long UInt;  // NOLINT
#endif  // GTEST_OS_WINDOWS
};


// This template class represents an IEEE floating-point number
// (either single-precision or double-precision, depending on the
// template parameters).
//
// The purpose of this class is to do more sophisticated number
// comparison.  (Due to round-off error, etc, it's very unlikely that
// two floating-points will be equal exactly.  Hence a naive
// comparison by the == operation often doesn't work.)
//
// Format of IEEE floating-point:
//
//   The most-significant bit being the leftmost, an IEEE
//   floating-point looks like
//
//     sign_bit exponent_bits fraction_bits
//
//   Here, sign_bit is a single bit that designates the sign of the
//   number.
//
//   For float, there are 8 exponent bits and 23 fraction bits.
//
//   For double, there are 11 exponent bits and 52 fraction bits.
//
//   More details can be found at
//   http://en.wikipedia.org/wiki/IEEE_floating-point_standard.
//
// Template parameter:
//
//   RawType: the raw floating-point type (either float or double)
template <typename RawType>
class FloatingPoint {
 public:
  // Defines the unsigned integer type that has the same size as the
  // floating point number.
  typedef typename TypeWithSize<sizeof(RawType)>::UInt Bits;

  // Constants.

  // # of bits in a number.
  static const size_t kBitCount = 8*sizeof(RawType);

  // # of fraction bits in a number.
  static const size_t kFractionBitCount =
    std::numeric_limits<RawType>::digits - 1;

  // # of exponent bits in a number.
  static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount;

  // The mask for the sign bit.
  static const Bits kSignBitMask = static_cast<Bits>(1) << (kBitCount - 1);

  // The mask for the fraction bits.
  static const Bits kFractionBitMask =
    ~static_cast<Bits>(0) >> (kExponentBitCount + 1);

  // The mask for the exponent bits.
  static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask);

  // How many ULP's (Units in the Last Place) we want to tolerate when
  // comparing two numbers.  The larger the value, the more error we
  // allow.  A 0 value means that two numbers must be exactly the same
  // to be considered equal.
  //
  // The maximum error of a single floating-point operation is 0.5
  // units in the last place.  On Intel CPU's, all floating-point
  // calculations are done with 80-bit precision, while double has 64
  // bits.  Therefore, 4 should be enough for ordinary use.
  //
  // See the following article for more details on ULP:
  // http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm.
  static const size_t kMaxUlps = 4;

  // Constructs a FloatingPoint from a raw floating-point number.
  //
  // On an Intel CPU, passing a non-normalized NAN (Not a Number)
  // around may change its bits, although the new value is guaranteed
  // to be also a NAN.  Therefore, don't expect this constructor to
  // preserve the bits in x when x is a NAN.
  explicit FloatingPoint(const RawType& x) { u_.value_ = x; }

  // Static methods

  // Reinterprets a bit pattern as a floating-point number.
  //
  // This function is needed to test the AlmostEquals() method.
  static RawType ReinterpretBits(const Bits bits) {
    FloatingPoint fp(0);
    fp.u_.bits_ = bits;
    return fp.u_.value_;
  }

  // Returns the floating-point number that represent positive infinity.
  static RawType Infinity() {
    return ReinterpretBits(kExponentBitMask);
  }

  // Non-static methods

  // Returns the bits that represents this number.
  const Bits &bits() const { return u_.bits_; }

  // Returns the exponent bits of this number.
  Bits exponent_bits() const { return kExponentBitMask & u_.bits_; }

  // Returns the fraction bits of this number.
  Bits fraction_bits() const { return kFractionBitMask & u_.bits_; }

  // Returns the sign bit of this number.
  Bits sign_bit() const { return kSignBitMask & u_.bits_; }

  // Returns true iff this is NAN (not a number).
  bool is_nan() const {
    // It's a NAN if the exponent bits are all ones and the fraction
    // bits are not entirely zeros.
    return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0);
  }

  // Returns true iff this number is at most kMaxUlps ULP's away from
  // rhs.  In particular, this function:
  //
  //   - returns false if either number is (or both are) NAN.
  //   - treats really large numbers as almost equal to infinity.
  //   - thinks +0.0 and -0.0 are 0 DLP's apart.
  bool AlmostEquals(const FloatingPoint& rhs) const {
    // The IEEE standard says that any comparison operation involving
    // a NAN must return false.
    if (is_nan() || rhs.is_nan()) return false;

    return DistanceBetweenSignAndMagnitudeNumbers(u_.bits_, rhs.u_.bits_)
        <= kMaxUlps;
  }

 private:
  // The data type used to store the actual floating-point number.
  union FloatingPointUnion {
    RawType value_;  // The raw floating-point number.
    Bits bits_;      // The bits that represent the number.
  };

  // Converts an integer from the sign-and-magnitude representation to
  // the biased representation.  More precisely, let N be 2 to the
  // power of (kBitCount - 1), an integer x is represented by the
  // unsigned number x + N.
  //
  // For instance,
  //
  //   -N + 1 (the most negative number representable using
  //          sign-and-magnitude) is represented by 1;
  //   0      is represented by N; and
  //   N - 1  (the biggest number representable using
  //          sign-and-magnitude) is represented by 2N - 1.
  //
  // Read http://en.wikipedia.org/wiki/Signed_number_representations
  // for more details on signed number representations.
  static Bits SignAndMagnitudeToBiased(const Bits &sam) {
    if (kSignBitMask & sam) {
      // sam represents a negative number.
      return ~sam + 1;
    } else {
      // sam represents a positive number.
      return kSignBitMask | sam;
    }
  }

  // Given two numbers in the sign-and-magnitude representation,
  // returns the distance between them as an unsigned number.
  static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits &sam1,
                                                     const Bits &sam2) {
    const Bits biased1 = SignAndMagnitudeToBiased(sam1);
    const Bits biased2 = SignAndMagnitudeToBiased(sam2);
    return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1);
  }

  FloatingPointUnion u_;
};

EDIT: Dieser Beitrag ist 4 Jahre alt. Es ist wahrscheinlich immer noch gültig und der Code ist nett, aber einige Leute haben Verbesserungen gefunden. Holen Sie sich am besten die neuste Version von AlmostEquals direkt aus dem Google Test-Quellcode und nicht aus dem, den ich hier eingefügt habe.

  • +1: Ich stimme zu, dass dieser richtig ist. Allerdings erklärt es nicht warum. Siehe hier: cygnus-software.com/papers/comparingfloats/comparingfloats.htm Ich habe diesen Blogbeitrag gelesen, nachdem ich hier meinen Kommentar zur Höchstpunktzahl geschrieben hatte; Ich glaube, es sagt dasselbe und bietet die rationale / Lösung, die oben implementiert ist. Weil es so viel Code gibt, werden die Leute die Antwort verpassen.

    – ungekünstelter Lärm

    17. März 2013 um 19:07 Uhr


  • Es gibt ein paar unangenehme Dinge, die passieren können, wenn implizite Umwandlungen auftreten, sagen wir FloatPoint fp(0.03f). Ich habe ein paar Änderungen daran vorgenommen, um dies zu verhindern. template explizit FloatingPoint(const U& x) { if(typeid(U).name() != typeid(RawType).name()) { std::cerr << "Du machst eine implizite Konvertierung mit FloatingPoint, nicht" << std::endl; assert(typeid(U).name() == typeid(RawType).name()); } u_.value_ = x; }

    – JeffCharter

    17. März 2014 um 21:16 Uhr

  • Guter Fund! Ich denke, es wäre am besten, sie zu Google Test beizutragen, wo dieser Code gestohlen wurde. Ich werde den Beitrag aktualisieren, um zu reflektieren, dass es wahrscheinlich eine neuere Version gibt. Wenn die Google-Jungs jucken, könnten Sie es zB in einen GitHub-Kerntext einfügen? Das verlinke ich dann auch.

    – skrebbel

    1. Mai 2014 um 12:48 Uhr


  • Bin ich der einzige, der “falsch” wird, wenn er die doppelte 0 mit 1e-16 vergleicht? Die voreingenommene Darstellung von 0 ist 9223372036854775808, während die voreingenommene Darstellung von 1e-16 13590969439990876604 ist. Es scheint eine Diskontinuität in der Darstellung zu sein, oder mache ich etwas falsch?

    – Gaston

    1. Oktober 2014 um 17:17 Uhr

  • Das neueste Code-Snippet finden Sie unter Hier und Hier.

    – Jaege

    25. November 2016 um 3:11 Uhr

1647291614 212 Was ist der effektivste Weg fur Gleitkomma und Doppelvergleiche
grom

Für einen tiefergehenden Ansatz lesen Sie Gleitkommazahlen vergleichen. Hier ist das Code-Snippet von diesem Link:

// Usable AlmostEqual function    
bool AlmostEqual2sComplement(float A, float B, int maxUlps)    
{    
    // Make sure maxUlps is non-negative and small enough that the    
    // default NAN won't compare as equal to anything.    
    assert(maxUlps > 0 && maxUlps < 4 * 1024 * 1024);    
    int aInt = *(int*)&A;    
    // Make aInt lexicographically ordered as a twos-complement int    
    if (aInt < 0)    
        aInt = 0x80000000 - aInt;    
    // Make bInt lexicographically ordered as a twos-complement int    
    int bInt = *(int*)&B;    
    if (bInt < 0)    
        bInt = 0x80000000 - bInt;    
    int intDiff = abs(aInt - bInt);    
    if (intDiff <= maxUlps)    
        return true;    
    return false;    
}

  • +1: Ich stimme zu, dass dieser richtig ist. Allerdings erklärt es nicht warum. Siehe hier: cygnus-software.com/papers/comparingfloats/comparingfloats.htm Ich habe diesen Blogbeitrag gelesen, nachdem ich hier meinen Kommentar zur Höchstpunktzahl geschrieben hatte; Ich glaube, es sagt dasselbe und bietet die rationale / Lösung, die oben implementiert ist. Weil es so viel Code gibt, werden die Leute die Antwort verpassen.

    – ungekünstelter Lärm

    17. März 2013 um 19:07 Uhr


  • Es gibt ein paar unangenehme Dinge, die passieren können, wenn implizite Umwandlungen auftreten, sagen wir FloatPoint fp(0.03f). Ich habe ein paar Änderungen daran vorgenommen, um dies zu verhindern. template explizit FloatingPoint(const U& x) { if(typeid(U).name() != typeid(RawType).name()) { std::cerr << "Du machst eine implizite Konvertierung mit FloatingPoint, nicht" << std::endl; assert(typeid(U).name() == typeid(RawType).name()); } u_.value_ = x; }

    – JeffCharter

    17. März 2014 um 21:16 Uhr

  • Guter Fund! Ich denke, es wäre am besten, sie zu Google Test beizutragen, wo dieser Code gestohlen wurde. Ich werde den Beitrag aktualisieren, um zu reflektieren, dass es wahrscheinlich eine neuere Version gibt. Wenn die Google-Jungs jucken, könnten Sie es zB in einen GitHub-Kerntext einfügen? Das verlinke ich dann auch.

    – skrebbel

    1. Mai 2014 um 12:48 Uhr


  • Bin ich der einzige, der “falsch” wird, wenn er die doppelte 0 mit 1e-16 vergleicht? Die voreingenommene Darstellung von 0 ist 9223372036854775808, während die voreingenommene Darstellung von 1e-16 13590969439990876604 ist. Es scheint eine Diskontinuität in der Darstellung zu sein, oder mache ich etwas falsch?

    – Gaston

    1. Oktober 2014 um 17:17 Uhr

  • Das neueste Code-Snippet finden Sie unter Hier und Hier.

    – Jaege

    25. November 2016 um 3:11 Uhr

Zu erkennen, dass dies ein alter Thread ist, aber dieser Artikel ist einer der geradlinigsten, den ich zum Vergleich von Gleitkommazahlen gefunden habe, und wenn Sie mehr erfahren möchten, enthält er auch detailliertere Referenzen und die Hauptseite deckt eine vollständige Reihe von Themen ab Umgang mit Gleitkommazahlen Der Gleitkomma-Leitfaden: Vergleich.

Einen etwas praktischeren Artikel finden wir in Fließkommatoleranzen überarbeitet und Notizen gibt es Absolute Toleranz test, was in C++ darauf hinausläuft:

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

und relative Toleranz Prüfung:

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 und y groß sind und im relativen Fall versagen, wenn sie klein sind. Unter der Annahme, dass absolute und relative Toleranz gleich sind, 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 ;
}

1003140cookie-checkWas ist der effektivste Weg für Gleitkomma- und Doppelvergleiche?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy