618 votes

Quelle est la méthode la plus efficace pour la comparaison des flottants et des doubles ?

Quel serait le moyen le plus efficace de comparer deux double ou deux float valeurs ?

Le simple fait de faire cela n'est pas correct :

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

Mais quelque chose comme :

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

Il semble que le traitement soit gaspillé.

Quelqu'un connaît-il un comparateur de flottes plus intelligent ?

3 votes

> serait-il plus efficace d'ajouter ... au début de la fonction ? <invoke Knuth> L'optimisation prématurée est la racine de tous les maux. </invoke Knuth> Il suffit d'utiliser abs(a-b) < EPS comme indiqué ci-dessus, c'est clair et facile à comprendre.

3 votes

Ici, c'est la manière mise en œuvre dans la bibliothèque de test Boost : http://www.boost.org/doc/libs/1_36_0/libs/test/doc/html/utf/testing-tools/floating_point_comparison.html

1 votes

Il semble que vous ayez sauté la dernière clause de votre citation -- "seulement après que ce code [critique] ait été identifié". À moins que ce code ne soit identifié comme un goulot d'étranglement, l'optimiser au-delà de la clarté est une perte de temps (et potentiellement nuisible).

522voto

Andrew Stein Points 6344

Soyez extrêmement prudent si vous utilisez l'une des autres suggestions. Tout dépend du contexte.

J'ai passé un long moment à tracer les bogues d'un système qui supposait a==b si |a-b|<epsilon . Les problèmes sous-jacents étaient les suivants :

  1. La présomption implicite dans un algorithme que si a==b y b==c puis a==c .

  2. Utiliser le même epsilon pour les lignes mesurées en pouces et les lignes mesurées en millièmes (.001 pouce). Soit a==b pero 1000a!=1000b . (C'est pourquoi AlmostEqual2sComplement demande l'epsilon ou le max ULPS).

  3. L'utilisation du même epsilon pour le cosinus des angles et la longueur des lignes !

  4. Utilisation d'une telle fonction de comparaison pour trier les éléments d'une collection. (Dans ce cas, l'utilisation de l'opérateur intégré C++ == pour les doubles a donné des résultats corrects).

Comme je l'ai dit : tout dépend du contexte et de la taille prévue de l'entreprise. a y b .

BTW, std::numeric_limits<double>::epsilon() est l'"epsilon machine". C'est la différence entre 1.0 et la prochaine valeur représentable par un double. Je suppose qu'il pourrait être utilisé dans la fonction de comparaison mais seulement si les valeurs attendues sont inférieures à 1. (Ceci est en réponse à la réponse de @cdv...)

Aussi, si vous avez essentiellement int l'arithmétique dans doubles (ici nous utilisons des doubles pour contenir des valeurs int dans certains cas) votre arithmétique sera correcte. Par exemple, 4.0/2.0 sera identique à 1.0+1.0. Ceci tant que vous ne faites pas de choses qui donnent des fractions (4.0/3.0) ou que vous ne dépassez pas la taille d'un int.

16 votes

+1 pour avoir souligné l'évidence (qui est souvent ignorée). Pour une méthode générique, vous pouvez rendre l'epsilon relatif à fabs(a)+fabs(b) mais avec la compensation pour les NaN, la somme de 0 et le dépassement, cela devient assez complexe.

4 votes

Il doit y avoir quelque chose que je ne comprends pas. Le typique float / double est MANTISSA x 2^ EXP . epsilon dépendra de l'exposant. Par exemple, si le mantisse est de 24bits et le exposant est signé sur 8 bits, alors 1/(2^24)*2^127 o ~2^103 est un epsilon pour certaines valeurs ; ou est-ce que cela fait référence à un minimum epsilon ?

4 votes

Attendez une seconde. Ce que j'ai dit, c'est ce que tu voulais dire ? Vous dites pourquoi |a-b|<epsilon c'est no correct. Veuillez ajouter ce lien à votre réponse ; si vous êtes d'accord cygnus-software.com/papers/comparingfloats/comparingfloats.htm et je peux retirer mes commentaires stupides.

196voto

OJ. Points 16939

La comparaison avec une valeur epsilon est ce que la plupart des gens font (même dans la programmation de jeux).

Vous devriez cependant modifier un peu votre mise en œuvre :

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

Edit : Christer a ajouté un tas d'excellentes informations sur ce sujet dans une article récent du blog . Profitez-en.

0 votes

@OJ : y a-t-il un problème avec le premier exemple de code ? Je pensais que le seul problème était dans une situation comme celle-ci : float a = 3.4; if(a == 3.4){...} c'est-à-dire lorsque vous comparez un nombre à virgule flottante stocké avec un nombre littéral | Dans ce cas, les deux nombres sont stockés, ils auront donc la même représentation, s'ils sont égaux, alors quel est le problème de faire a == b ?

0 votes

13 votes

@DonReba : Seulement si EPSILON est défini comme suit DBL_EPSILON . Normalement, il s'agit d'une valeur spécifique choisie en fonction de la précision requise pour la comparaison.

140voto

mch Points 3583

La comparaison de nombres à virgule flottante pour dépend du contexte. Étant donné que même le fait de changer l'ordre des opérations peut produire des résultats différents, il est important de savoir à quel point vous voulez que les nombres soient "égaux".

Comparaison de nombres à virgule flottante de Bruce Dawson est un bon point de départ pour étudier la comparaison en virgule flottante.

Les définitions suivantes sont tirées de L'art de la programmation informatique par Knuth :

bool approximatelyEqual(float a, float b, float epsilon)
{
    return fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

bool essentiallyEqual(float a, float b, float epsilon)
{
    return fabs(a - b) <= ( (fabs(a) > fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

bool definitelyGreaterThan(float a, float b, float epsilon)
{
    return (a - b) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

bool definitelyLessThan(float a, float b, float epsilon)
{
    return (b - a) > ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
}

Bien entendu, le choix d'epsilon dépend du contexte et détermine le degré d'égalité des chiffres que vous souhaitez obtenir.

Une autre méthode de comparaison des nombres à virgule flottante consiste à examiner les ULP (unités en dernière place) des nombres. Bien que ne traitant pas spécifiquement des comparaisons, le document Ce que tout informaticien devrait savoir sur les nombres à virgule flottante est une bonne ressource pour comprendre comment fonctionne la virgule flottante et quels sont les pièges, y compris ce qu'est ULP.

2 votes

Merci d'avoir affiché comment déterminer quel chiffre est le plus petit/le plus grand !

1 votes

fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon); a sauvé ma vie. LOL Notez que cette version (je n'ai pas vérifié si elle s'applique aussi aux autres) prend également en compte le changement qui peut se produire dans la partie intégrale du nombre à virgule flottante (exemple : 2147352577.9999997616 == 2147352576.0000000000 où vous pouvez clairement voir qu'il y a presque une différence de 2 entre les deux chiffres), ce qui est plutôt agréable ! Cela se produit lorsque l'erreur d'arrondi accumulée déborde la partie décimale du nombre.

0 votes

Un article très intéressant et utile de Bruce Dawson, merci !

120voto

skrebbel Points 5183

J'ai trouvé que le Cadre de test C++ de Google contient une belle implémentation multi-plateforme de AlmostEqual2sComplement basée sur des modèles, qui fonctionne à la fois sur les doubles et les flottants. Étant donné qu'il est publié sous la licence BSD, son utilisation dans votre propre code ne devrait pas poser de problème, tant que vous conservez la licence. J'ai extrait le code ci-dessous de 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 et ajouté la licence sur le dessus.

Assurez-vous de #definir GTEST_OS_WINDOWS à une valeur quelconque (ou de changer le code où il est utilisé à quelque chose qui correspond à votre base de code - c'est une licence BSD après tout).

Exemple d'utilisation :

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

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

Voici le 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: wan@google.com (Zhanyong Wan), eefacm@gmail.com (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 : Ce post a 4 ans. Il est probablement toujours valable, et le code est agréable, mais certaines personnes ont trouvé des améliorations. Le mieux est d'aller chercher la dernière version de AlmostEquals directement depuis le code source de Google Test, et non celui que j'ai collé ici.

3 votes

+1 : Je suis d'accord pour dire que celle-ci est correcte. Cependant, il n'explique pas pourquoi. Voir ici : cygnus-software.com/papers/comparingfloats/comparingfloats.htm J'ai lu cet article de blog après avoir écrit mon commentaire sur le meilleur score ici ; je crois qu'il dit la même chose et fournit le rationnel/solution qui est mis en œuvre ci-dessus. Comme il y a beaucoup de code, les gens vont manquer la réponse.

0 votes

Il y a un certain nombre de choses désagréables qui peuvent se produire lorsque des casts implicites se produisent en faisant par exemple FloatPoint<double> fp(0.03f). J'ai fait quelques modifications pour éviter cela. template<typename U> explicit FloatingPoint(const U& x) { if(typeid(U).name() != typeid(RawType).name()) { std::cerr << "Vous faites une conversion implicite avec FloatingPoint, Ne" << std::endl ; assert(typeid(U).name() == typeid(RawType).name()) ; } u_.value_ = x ; }

2 votes

Bonne découverte ! Je pense que le mieux serait de les mettre à disposition de Google Test, d'où ce code a été volé. Je vais mettre à jour le post pour refléter qu'il y a probablement une version plus récente. Si les gars de Google ont la bougeotte, vous pourriez le mettre dans un gist GitHub par exemple ? Je mettrai un lien vers ce dernier dans ce cas.

50voto

grom Points 8057

Pour une approche plus approfondie, lisez Comparaison de nombres à virgule flottante . Voici l'extrait de code de ce lien :

// 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;    
}

15 votes

Quelle est la valeur suggérée de maxUlps ?

6 votes

Will " *(int*)&A; " viole la règle stricte de l'aliasing ?

0 votes

Non. C'est de la copie, pas de l'aliasing.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X