Quel est le moyen le plus efficace pour la comparaison flottante et double?

524

Quelle serait la façon la plus efficace de comparer deux doubleou deux floatvaleurs?

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

Semble au traitement des déchets.

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

Alex
la source
2
> serait-il plus efficace d'ajouter ... au début de la fonction? <invoke Knuth>L'optimisation prématurée est la racine de tout Mal. </invoke Knuth>Allez simplement avec abs (ab) <EPS comme indiqué ci-dessus, c'est clair et facile à comprendre.
Andrew Coleson
2
Voici la manière implémentée dans la bibliothèque de tests Boost: http://www.boost.org/doc/libs/1_36_0/libs/test/doc/html/utf/testing-tools/floating_point_comparison.html
Alessandro Jacopson
2
La seule chose non optimale dans l'implémentation de l'affiche originale est qu'elle contient une branche supplémentaire à &&. La réponse d'OJ est optimale. fabs est un intrinsèque qui est une seule instruction sur x87, et je suppose que sur presque tout le reste aussi. Acceptez déjà la réponse d'OJ!
3yE
3
Si vous le pouvez, supprimez la virgule flottante et utilisez des points fixes. Exemple, utilisez {virgule fixe} millimètres au lieu de {virgule flottante} mètres.
Thomas Matthews
33
"Faire simplement cela n'est pas correct" - Ce ne sont que des ordures, bien sûr, l'utilisation ==peut être parfaitement correcte, mais cela dépend entièrement du contexte non donné dans la question. Jusqu'à ce que ce contexte soit connu, ==il reste le "moyen le plus efficace" .
Christian Rau

Réponses:

459

Soyez extrêmement prudent en utilisant l'une des autres suggestions. Tout dépend du contexte.

J'ai passé beaucoup de temps à retracer un bogue dans un système qui présumait a==bsi |a-b|<epsilon. Les problèmes sous-jacents étaient les suivants:

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

  2. Utiliser le même epsilon pour les lignes mesurées en pouces et les lignes mesurées en mils (0,001 pouce). Ce n'est a==bque 1000a!=1000b. (C'est pourquoi AlmostEqual2sComplement demande le epsilon ou le max ULPS).

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

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

Comme je l'ai dit: tout dépend du contexte et de la taille attendue de aet b.

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

De plus, si vous avez essentiellement de l' intarithmétique 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. C'est aussi longtemps que vous ne faites pas de choses qui aboutissent à des fractions (4.0 / 3.0) ou ne sortez pas de la taille d'un int.

Andrew Stein
la source
10
+1 pour signaler l'évidence (qui est souvent ignorée). Pour une méthode générique, vous pouvez faire le epsilon par rapport à fabs(a)+fabs(b)mais avec la compensation de NaN, 0 somme et débordement, cela devient assez complexe.
peterchen
4
Il doit y avoir quelque chose que je ne comprends pas. Le typique float/ doubleest MANTISSA x 2 ^ EXP . epsilondépendra de l'exposant. Par exemple, si la mantisse est 24 bits et l' exposant est signé 8 bits, alors 1/(2^24)*2^127ou ~2^103est un epsilonpour certaines valeurs; ou s'agit-il d'un epsilon minimum ?
bruit sans art
3
Attends une seconde. Est-ce que j'ai dit ce que tu voulais dire? Vous dites pourquoi |a-b|<epsilon, n'est pas correct. Veuillez ajouter ce lien à votre réponse; si vous acceptez cygnus-software.com/papers/comparingfloats/comparingfloats.htm et que je peux supprimer mes commentaires stupides.
bruit sans art
3
C'est un très long commentaire, pas une réponse en soi. Existe-t-il un (ensemble de) réponse (s) canonique (s) pour tous les contextes?
Merlyn Morgan-Graham
2
L'ancien lien semble obsolète, la nouvelle page est ici randomascii.wordpress.com/2012/02/25/…
Marson Mao
174

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

Vous devez cependant modifier un peu votre implémentation:

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

Edit: Christer a ajouté une pile d'excellentes informations sur ce sujet dans un récent article de blog . Prendre plaisir.

JO.
la source
@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 point flottant stocké avec un littéral | Dans ce cas, les deux nombres sont stockés, donc ils auront la même représentation, s'ils sont égaux, alors quel est le mal à faire a == b?
Lazer
11
@DonReba: Seulement si EPSILONest défini comme DBL_EPSILON. Normalement, ce sera une valeur spécifique choisie en fonction de la précision requise de la comparaison.
Nemo157
7
EPSILONla comparaison ne fonctionne pas lorsque les flotteurs sont grands, car la différence entre les flotteurs consécutifs devient également grande. Voir cet article .
kevintodisco
22
Pas étonnant qu'il y ait des combats Z dans certains jeux lorsque des textures / objets lointains scintillent, comme dans Battlefield 4. Comparer la différence avec EPSILONest à peu près inutile. Vous devez comparer avec un seuil qui a du sens pour les unités à portée de main. Aussi, utilisez-le std::abscar il est surchargé pour différents types de virgule flottante.
Maxim Egorushkin
11
J'ai rétrogradé car l'exemple de code montre que le bug typique est répété par la majorité des programmeurs. Le point flottant concerne toujours les erreurs relatives, car il s'agit d'un point flottant (et non d'un point fixe). Il ne fonctionnera donc jamais correctement avec une erreur fixe (epsilon).
user2261015
115

J'ai trouvé que le cadre de test Google C ++ contient une belle implémentation basée sur un modèle multiplateforme d'AlmostEqual2sComplement 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 en haut.

Assurez-vous de #define GTEST_OS_WINDOWS à une certaine valeur (ou de changer le code où il est utilisé pour 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: [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: Ce poste a 4 ans. Il est probablement toujours valide et le code est agréable, mais certaines personnes ont trouvé des améliorations. Il vaut mieux obtenir la dernière version du AlmostEqualsdroit à partir du code source de Google Test, et non celle que j'ai collée ici.

skrebbel
la source
3
+1: Je suis d'accord que celui-ci est correct. Cependant, cela 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 que cela dit la même chose et fournit la solution / rationnelle qui est mise en œuvre ci-dessus. Parce qu'il y a tellement de code, les gens vont manquer la réponse.
bruit sans art
Il y a quelques choses désagréables qui peuvent se produire lorsque des transtypages implicites se produisent en faisant par exemple FloatPoint <double> fp (0.03f). J'ai apporté quelques modifications à cela pour éviter cela. template <typename U> explicit FloatingPoint (const U & x) {if (typeid (U) .name ()! = typeid (RawType) .name ()) {std :: cerr << "Vous effectuez une conversion implicite avec FloatingPoint, Don't "<< std :: endl; assert (typeid (U) .name () == typeid (RawType) .name ()); } u_.value_ = x; }
JeffCharter
2
Bonne trouvaille! Je suppose qu'il serait préférable de les contribuer à Google Test, où ce code a été volé. Je mettrai à jour le message pour refléter qu'il existe probablement une version plus récente. Si les gars de Google démangent, pourriez-vous le mettre, par exemple, dans un aperçu de GitHub? Je vais également faire un lien avec cela, alors.
skrebbel
3
Pour l'extrait de code le plus récent, voir ici et ici .
Jaege
1
J'ai extrait les lignes nécessaires dans un fichier gist. Tout le monde peut atteindre d' ici .
Yusuf Tarık Günaydın
111

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

La comparaison des nombres à virgule flottante par Bruce Dawson est un bon point de départ lorsque l'on regarde la comparaison à 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 sûr, choisir epsilon dépend du contexte et détermine dans quelle mesure vous voulez que les nombres soient égaux.

Une autre méthode de comparaison des nombres à virgule flottante consiste à examiner les ULP (unités à la dernière place) des nombres. Bien qu'il ne traite 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 le virgule flottante et quels sont les pièges, y compris ce qu'est ULP.

mch
la source
1
merci d'avoir posté comment déterminer quel nombre est plus petit / plus grand!
Tomato
1
fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);sauvé ma vie. LOL Notez que cette version (je n'ai pas vérifié si cela s'applique également aux autres) prend également en compte le changement qui pourrait se produire dans la partie intégrante du nombre à virgule flottante (exemple: 2147352577.9999997616 == 2147352576.0000000000où vous pouvez clairement voir qu'il y a presque une différence de 2entre les deux nombres) ce qui est plutôt sympa! Cela se produit lorsque l'erreur d'arrondi accumulée déborde de la partie décimale du nombre.
rbaleksandar
Article très agréable et utile de Bruce Dawson, merci!
BobMorane
2
Étant donné que cette question est étiquetée C ++, vos chèques seraient plus faciles à lire étant écrits comme std::max(std::abs(a), std::abs(b))(ou avec std::min()); std::absen C ++ est surchargé avec float & double types, donc cela fonctionne très bien (vous pouvez toujours garder fabspour la lisibilité cependant).
Razakhel
1
Il s'avère que le problème était dans mon code, différence entre la valeur attendue d'origine et la chaîne analysée.
mwpowellhtx
47

Pour une approche plus approfondie, lisez Comparaison des 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;    
}
grom
la source
14
Quelle est la valeur suggérée de maxUlps?
unj2
6
" *(int*)&A;" Violera-t-il la règle stricte d'alias?
osgx
3
Selon gtest (recherche ULP), 4 est un nombre acceptable.
May Oakes
4
Et voici quelques mises à jour de l'article de Bruce Dawson (dont l'une est liée dans l'intro de l'article): randomascii.wordpress.com/2012/02/25/… et randomascii.wordpress.com/2012/06/26/…
Michael Burr
3
Il m'a fallu un certain temps pour comprendre ce qu'était ULP: Unités à la dernière place
JeffCharter
27

Réalisant que c'est un vieux fil, mais cet article est l'un des plus simples que j'ai trouvé en comparant les nombres à virgule flottante et si vous voulez en savoir plus, il contient également des références plus détaillées et le site principal couvre une gamme complète de problèmes gestion des nombres à virgule flottante Le guide des virgules flottantes: comparaison .

Nous pouvons trouver un article un peu plus pratique dans Tolérances en virgule flottante revisité et note qu'il existe un test de tolérance absolue , qui se résume à cela en C ++:

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

et test de tolérance relative :

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

L'article note que le test absolu échoue quand xet ysont grands et échoue dans le cas relatif quand ils sont petits. En supposant que la tolérance absolue et relative est la même, un test combiné ressemblerait à ceci:

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 ;
}
Shafik Yaghmour
la source
25

La façon portable d'obtenir epsilon en C ++ est

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

Ensuite, la fonction de comparaison devient

#include <cmath>
#include <limits>

bool AreSame(double a, double b) {
    return std::fabs(a - b) < std::numeric_limits<double>::epsilon();
}
Chris de Vries
la source
34
Vous voudrez probablement un multiple de cet epsilon.
user7116
11
Ne pouvez-vous pas simplement utiliser std :: abs? AFAIK, std :: abs est également surchargé pour les doubles. Veuillez m'avertir si je me trompe.
kolistivra
3
@kolistivra, vous vous trompez. Le «f» dans «fabs» ne signifie pas le type float. Vous pensez probablement aux fonctions C fabsf () et fabsl ().
jcoffland
9
En fait, pour les raisons décrites dans l'article de Bruce, epsilon change à mesure que la valeur en virgule flottante augmente. Voir la partie où il dit "Pour les nombres supérieurs à 2,0, l'écart entre les flotteurs augmente et si vous comparez les flotteurs en utilisant FLT_EPSILON, vous effectuez simplement une vérification d'égalité plus coûteuse et moins évidente."
bobobobo
5
je sais que c'est vieux mais std :: abs est surchargé pour les types à virgule flottante dans cmath.
mholzmann
18

J'ai fini par passer pas mal de temps à parcourir le matériel dans ce grand fil. Je doute que tout le monde veuille passer autant de temps, je voudrais donc souligner le résumé de ce que j'ai appris et la solution que j'ai mise en œuvre.

Résumé rapide

  1. 1e-8 est-il approximativement identique à 1e-16? Si vous regardez des données de capteurs bruyantes, alors probablement oui, mais si vous faites de la simulation moléculaire, ce n'est peut-être pas le cas! Conclusion: vous devez toujours penser à la valeur de tolérance dans le contexte d'un appel de fonction spécifique et pas seulement en faire une constante codée en dur générique à l'échelle de l'application.
  2. Pour les fonctions de bibliothèque générales, il est toujours agréable d'avoir un paramètre avec une tolérance par défaut . Un choix typique numeric_limits::epsilon()est le même que FLT_EPSILON dans float.h. Ceci est cependant problématique car epsilon pour comparer des valeurs comme 1.0 n'est pas identique à epsilon pour des valeurs comme 1E9. Le FLT_EPSILON est défini pour 1.0.
  3. L'implémentation évidente pour vérifier si le nombre est dans la tolérance est fabs(a-b) <= epsiloncependant que cela ne fonctionne pas car epsilon par défaut est défini pour 1.0. Nous devons augmenter ou diminuer epsilon en termes de a et b.
  4. Il existe deux solutions à ce problème: soit vous définissez epsilon proportionnellement à, max(a,b)soit vous pouvez obtenir les prochains nombres représentables autour de a, puis voir si b tombe dans cette plage. La première est appelée méthode "relative" et plus tard est appelée méthode ULP.
  5. Les deux méthodes échouent en fait de toute façon lors de la comparaison avec 0. Dans ce cas, l'application doit fournir une tolérance correcte.

Implémentation des fonctions utilitaires (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;
}
Shital Shah
la source
isDefinitelyLessThanvérifie diff < tolerance, ce qui signifie que a et b sont presque égaux (et donc a n'est certainement pas inférieur à b). N'est-il pas plus logique de vérifier la tolérance diff> dans les deux cas? Ou peut-être ajoutez un orEqualToargument qui contrôle si la vérification d'égalité approximative doit renvoyer true ou non.
Matt Chambers
14

Le code que vous avez écrit est buggé:

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

Le code correct serait:

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

(... et oui c'est différent)

Je me demande si les fabs ne vous feraient pas perdre l'évaluation paresseuse dans certains cas. Je dirais que cela dépend du compilateur. Vous voudrez peut-être essayer les deux. S'ils sont en moyenne équivalents, prenez l'implémentation avec des fabs.

Si vous avez des informations sur lequel des deux flotteurs est plus susceptible d'être plus grand que les autres, vous pouvez jouer dans l'ordre de la comparaison pour mieux profiter de l'évaluation paresseuse.

Enfin, vous pourriez obtenir de meilleurs résultats en intégrant cette fonction. Peu susceptible de s'améliorer cependant ...

Edit: OJ, merci d'avoir corrigé votre code. J'ai effacé mon commentaire en conséquence

fulmicoton
la source
13

`retour fabs (a - b) <EPSILON;

C'est très bien si:

  • l'ordre de grandeur de vos entrées ne change pas grand-chose
  • un très petit nombre de signes opposés peut être considéré comme égal

Mais sinon, cela vous mènera à des ennuis. Les nombres à double précision ont une résolution d'environ 16 décimales. Si les deux nombres que vous comparez sont plus grands que EPSILON * 1.0E16, alors vous pourriez aussi bien dire:

return a==b;

J'examinerai une approche différente qui suppose que vous devez vous soucier du premier problème et que le second convient à votre application. Une solution serait quelque chose comme:

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

C'est coûteux en calcul, mais c'est parfois ce qui est demandé. C'est ce que nous devons faire dans mon entreprise car nous avons affaire à une bibliothèque d'ingénierie et les intrants peuvent varier de quelques dizaines d'ordre de grandeur.

Quoi qu'il en soit, le point est le suivant (et s'applique à pratiquement tous les problèmes de programmation): Évaluez vos besoins, puis trouvez une solution pour répondre à vos besoins - ne présumez pas que la réponse facile répondra à vos besoins. Si après votre évaluation, vous trouvez que fabs(a-b) < EPSILONcela suffira, parfait - utilisez-le! Mais soyez conscient de ses lacunes et des autres solutions possibles.


la source
3
Mis à part les fautes de frappe (s / - /, / virgule manquante dans fmax ()), cette implémentation a un bogue pour les nombres proches de zéro qui se trouvent dans EPSILON, mais pas encore tout à fait VERYSMALL. Par exemple, AreSame (1.0E-10, 1.0E-9) signale faux car l'erreur relative est énorme. Vous devenez le héros de votre entreprise.
brlcad
1
@brlcad Vous n'avez pas obtenu le point de virgule flottante . 1.0E-10 et 1.0E-9 diffèrent par la magnitude de 10. Il est donc vrai qu'ils ne sont pas les mêmes. la virgule flottante concerne toujours les erreurs relatives . Si vous avez un système qui considère 1.0E-10 et 1.0E-9 comme presque égaux, car les deux sont "assez proches de zéro" (ce qui semble raisonnable pour l'homme mais n'a rien de mathématique), alors EPSILON doit être ajusté comme il convient. pour un tel système.
user2261015
8

Comme d'autres l'ont souligné, l'utilisation d'un epsilon à exposant fixe (tel que 0,000000001) sera inutile pour les valeurs éloignées de la valeur epsilon. Par exemple, si vos deux valeurs sont 10000.000977 et 10000, il n'y a AUCUNE valeur à virgule flottante de 32 bits entre ces deux nombres - 10000 et 10000.000977 sont aussi proches que possible sans être identiques bit par bit. Ici, un epsilon inférieur à 0,0009 n'a pas de sens; vous pourriez aussi bien utiliser l'opérateur d'égalité directe.

De même, à mesure que les deux valeurs approchent epsilon, l'erreur relative augmente à 100%.

Ainsi, essayer de mélanger un nombre à virgule fixe tel que 0,00001 avec des valeurs à virgule flottante (où l'exposant est arbitraire) est un exercice inutile. Cela ne fonctionnera que si vous pouvez être assuré que les valeurs d'opérande se trouvent dans un domaine étroit (c'est-à-dire près d'un exposant spécifique) et si vous sélectionnez correctement une valeur epsilon pour ce test spécifique. Si vous sortez un numéro de l'air ("Hé! 0.00001 est petit, donc ça doit être bon!"), Vous êtes condamné à des erreurs numériques. J'ai passé beaucoup de temps à déboguer un mauvais code numérique où un pauvre schmuck lance des valeurs epsilon aléatoires pour faire fonctionner un autre cas de test.

Si vous effectuez une programmation numérique de quelque nature que ce soit et que vous pensez que vous devez atteindre des epsilons à virgule fixe, LISEZ L'ARTICLE DE BRUCE SUR LA COMPARAISON DES NUMÉROS À POINTS FLOTTANTS .

Comparaison des nombres à virgule flottante

Steve Hollasch
la source
5

Qt implémente deux fonctions, vous pouvez peut-être en tirer des leçons:

static inline bool qFuzzyCompare(double p1, double p2)
{
    return (qAbs(p1 - p2) <= 0.000000000001 * qMin(qAbs(p1), qAbs(p2)));
}

static inline bool qFuzzyCompare(float p1, float p2)
{
    return (qAbs(p1 - p2) <= 0.00001f * qMin(qAbs(p1), qAbs(p2)));
}

Et vous pourriez avoir besoin des fonctions suivantes, puisque

Notez que la comparaison des valeurs où p1 ou p2 est 0.0 ne fonctionnera pas, pas plus que la comparaison des valeurs où l'une des valeurs est NaN ou l'infini. Si l'une des valeurs est toujours 0,0, utilisez plutôt qFuzzyIsNull. Si l'une des valeurs est probablement 0,0, une solution consiste à ajouter 1,0 aux deux valeurs.

static inline bool qFuzzyIsNull(double d)
{
    return qAbs(d) <= 0.000000000001;
}

static inline bool qFuzzyIsNull(float f)
{
    return qAbs(f) <= 0.00001f;
}
Dana Yan
la source
3

La comparaison à usage général des nombres à virgule flottante n'a généralement aucun sens. Comment comparer dépend vraiment d'un problème à portée de main. Dans de nombreux problèmes, les nombres sont suffisamment discrétisés pour permettre de les comparer à l'intérieur d'une tolérance donnée. Malheureusement, il y a autant de problèmes, où une telle astuce ne fonctionne pas vraiment. Par exemple, envisagez de travailler avec une fonction Heaviside (étape) d'un nombre en question (les options sur actions numériques viennent à l'esprit) lorsque vos observations sont très proches de la barrière. Effectuer une comparaison basée sur la tolérance ne serait pas très utile, car cela déplacerait efficacement le problème de la barrière d'origine à deux nouvelles. Encore une fois, il n'y a pas de solution générale à de tels problèmes et la solution particulière pourrait nécessiter d'aller jusqu'à changer la méthode numérique afin d'atteindre la stabilité.


la source
3

Malheureusement, même votre code "inutile" est incorrect. EPSILON est la plus petite valeur qui pourrait être ajoutée à 1.0 et changer sa valeur. La valeur 1.0 est très importante - les nombres plus importants ne changent pas lorsqu'ils sont ajoutés à EPSILON. Maintenant, vous pouvez adapter cette valeur aux nombres que vous comparez pour savoir s'ils sont différents ou non. L'expression correcte pour comparer deux doubles est:

if (fabs(a - b) <= DBL_EPSILON * fmax(fabs(a), fabs(b)))
{
    // ...
}

C'est au minimum. En général, cependant, vous voudriez tenir compte du bruit dans vos calculs et ignorer quelques-uns des bits les moins significatifs, donc une comparaison plus réaliste ressemblerait à:

if (fabs(a - b) <= 16 * DBL_EPSILON * fmax(fabs(a), fabs(b)))
{
    // ...
}

Si les performances de comparaison sont très importantes pour vous et que vous connaissez la plage de vos valeurs, vous devez plutôt utiliser des nombres à virgule fixe.

Don Reba
la source
2
"EPSILON est la plus petite valeur qui pourrait être ajoutée à 1.0 et changer sa valeur": En fait, cet honneur revient au successeur de 0,5 * EPSILON (dans le mode par défaut arrondi au plus proche). blog.frama-c.com/index.php?post/2013/05/09/FLT_EPSILON
Pascal Cuoq
Pourquoi pensez-vous que EPSILONdans la question est DBL_EPSILONou FLT_EPSILON? Le problème est dans votre propre imagination, où vous avez substitué DBL_EPSILON(ce qui serait en effet le mauvais choix) dans un code qui ne l'a pas utilisé.
Ben Voigt
@BenVoigt, vous avez raison, c'était quelque chose dans mon esprit à l'époque, et j'ai interprété la question dans cette optique.
Don Reba
2

Ma classe basée sur les réponses précédemment publiées. Très similaire au code de Google mais j'utilise un biais qui pousse toutes les valeurs NaN au-dessus de 0xFF000000. Cela permet une vérification plus rapide de NaN.

Ce code est destiné à démontrer le concept, et non à être une solution générale. Le code de Google montre déjà comment calculer toutes les valeurs spécifiques à la plate-forme et je ne voulais pas dupliquer tout cela. J'ai fait des tests limités sur ce code.

typedef unsigned int   U32;
//  Float           Memory          Bias (unsigned)
//  -----           ------          ---------------
//   NaN            0xFFFFFFFF      0xFF800001
//   NaN            0xFF800001      0xFFFFFFFF
//  -Infinity       0xFF800000      0x00000000 ---
//  -3.40282e+038   0xFF7FFFFF      0x00000001    |
//  -1.40130e-045   0x80000001      0x7F7FFFFF    |
//  -0.0            0x80000000      0x7F800000    |--- Valid <= 0xFF000000.
//   0.0            0x00000000      0x7F800000    |    NaN > 0xFF000000
//   1.40130e-045   0x00000001      0x7F800001    |
//   3.40282e+038   0x7F7FFFFF      0xFEFFFFFF    |
//   Infinity       0x7F800000      0xFF000000 ---
//   NaN            0x7F800001      0xFF000001
//   NaN            0x7FFFFFFF      0xFF7FFFFF
//
//   Either value of NaN returns false.
//   -Infinity and +Infinity are not "close".
//   -0 and +0 are equal.
//
class CompareFloat{
public:
    union{
        float     m_f32;
        U32       m_u32;
    };
    static bool   CompareFloat::IsClose( float A, float B, U32 unitsDelta = 4 )
                  {
                      U32    a = CompareFloat::GetBiased( A );
                      U32    b = CompareFloat::GetBiased( B );

                      if ( (a > 0xFF000000) || (b > 0xFF000000) )
                      {
                          return( false );
                      }
                      return( (static_cast<U32>(abs( a - b ))) < unitsDelta );
                  }
    protected:
    static U32    CompareFloat::GetBiased( float f )
                  {
                      U32    r = ((CompareFloat*)&f)->m_u32;

                      if ( r & 0x80000000 )
                      {
                          return( ~r - 0x007FFFFF );
                      }
                      return( r + 0x7F800000 );
                  }
};
WaterbugDesign
la source
2

Voici la preuve que l'utilisation std::numeric_limits::epsilon() n'est pas la réponse - elle échoue pour les valeurs supérieures à un:

Preuve de mon commentaire ci-dessus:

#include <stdio.h>
#include <limits>

double ItoD (__int64 x) {
    // Return double from 64-bit hexadecimal representation.
    return *(reinterpret_cast<double*>(&x));
}

void test (__int64 ai, __int64 bi) {
    double a = ItoD(ai), b = ItoD(bi);
    bool close = std::fabs(a-b) < std::numeric_limits<double>::epsilon();
    printf ("%.16f and %.16f %s close.\n", a, b, close ? "are " : "are not");
}

int main()
{
    test (0x3fe0000000000000L,
          0x3fe0000000000001L);

    test (0x3ff0000000000000L,
          0x3ff0000000000001L);
}

L'exécution donne cette sortie:

0.5000000000000000 and 0.5000000000000001 are  close.
1.0000000000000000 and 1.0000000000000002 are not close.

Notez que dans le second cas (un et juste supérieur à un), les deux valeurs d'entrée sont aussi proches que possible, et comparent toujours comme non proches. Ainsi, pour des valeurs supérieures à 1,0, vous pourriez tout aussi bien utiliser un test d'égalité. Les epsilons fixes ne vous sauveront pas lors de la comparaison de valeurs à virgule flottante.

Steve Hollasch
la source
Je crois return *(reinterpret_cast<double*>(&x));que même si cela fonctionne généralement, c'est en fait un comportement indéfini.
Jaap Versteegh
Bon point, bien que ce code soit illustratif, donc suffisant pour démontrer le problème du numeric_limits<>::epsilonpoint de revêtement de sol IEEE 754.
Steve Hollasch
Également un bon point, mais il n'est pas sage de publier un message sur le débordement de la pile en attendant ce genre de compréhension. Le code sera copié à l'aveugle, ce qui rendra encore plus difficile l'éradication de ce modèle très courant - en même temps que l'astuce d'union - qui devrait être évitée comme tous les UD devraient le faire.
Jaap Versteegh
1

Trouvé une autre implémentation intéressante sur: https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon

#include <cmath>
#include <limits>
#include <iomanip>
#include <iostream>
#include <type_traits>
#include <algorithm>



template<class T>
typename std::enable_if<!std::numeric_limits<T>::is_integer, bool>::type
    almost_equal(T x, T y, int ulp)
{
    // the machine epsilon has to be scaled to the magnitude of the values used
    // and multiplied by the desired precision in ULPs (units in the last place)
    return std::fabs(x-y) <= std::numeric_limits<T>::epsilon() * std::fabs(x+y) * ulp
        // unless the result is subnormal
        || std::fabs(x-y) < std::numeric_limits<T>::min();
}

int main()
{
    double d1 = 0.2;
    double d2 = 1 / std::sqrt(5) / std::sqrt(5);
    std::cout << std::fixed << std::setprecision(20) 
        << "d1=" << d1 << "\nd2=" << d2 << '\n';

    if(d1 == d2)
        std::cout << "d1 == d2\n";
    else
        std::cout << "d1 != d2\n";

    if(almost_equal(d1, d2, 2))
        std::cout << "d1 almost equals d2\n";
    else
        std::cout << "d1 does not almost equal d2\n";
}
Prashant Nidgunde
la source
0

Je me méfierais beaucoup de ces réponses qui impliquent une soustraction à virgule flottante (par exemple, fabs (ab) <epsilon). Premièrement, les nombres à virgule flottante deviennent plus clairsemés à de plus grandes amplitudes et à des amplitudes suffisamment élevées où l'espacement est supérieur à epsilon, vous pourriez tout aussi bien faire a == b. Deuxièmement, la soustraction de deux nombres à virgule flottante très proches (comme ceux-ci auront tendance à l'être, étant donné que vous recherchez une quasi-égalité) est exactement la façon dont vous obtenez une annulation catastrophique .

Bien que non portable, je pense que la réponse de grom fait le meilleur travail pour éviter ces problèmes.

Boojum
la source
1
+1 pour une bonne information. Cependant, je ne vois pas comment vous pourriez gâcher la comparaison d'égalité en augmentant l'erreur relative; À mon humble avis, l'erreur ne devient significative que dans le résultat de la soustraction, mais son ordre de grandeur par rapport à celui des deux opérandes soustraits devrait toujours être suffisamment fiable pour juger de l'égalité. Sauf si la résolution doit être globalement plus élevée, mais dans ce cas, la seule solution est de passer à une représentation en virgule flottante avec des bits plus significatifs dans la mantisse.
sehe
La soustraction de deux nombres presque égaux ne conduit PAS à une annulation catastrophique - en fait, elle n'introduit aucune erreur (Théorème de qv Sterbenz). L'annulation catastrophique se produit plus tôt, pendant le calcul de aet beux - mêmes. Il n'y a absolument aucun problème à utiliser la soustraction à virgule flottante dans le cadre d'une comparaison floue (bien que comme d'autres l'ont dit, une valeur epsilon absolue peut ou non être appropriée pour un cas d'utilisation donné.)
Sneftel
0

Il existe en fait des cas dans les logiciels numériques où vous voulez vérifier si deux nombres à virgule flottante sont exactement égaux. J'ai posté ceci sur une question similaire

https://stackoverflow.com/a/10973098/1447411

Vous ne pouvez donc pas dire que "CompareDoubles1" est faux en général.

Michael Lehn
la source
En fait, une référence très solide à une bonne réponse, bien qu'il soit très spécialisé de limiter toute personne sans fond de calcul scientifique ou d'analyse numérique (Ie LAPACK, BLAS) à ne pas comprendre l'intégralité. Ou en d'autres termes, cela suppose que vous avez lu quelque chose comme l' introduction de recettes numériques ou l' analyse numérique par Burden & Faires.
mctylr
0

Cela dépend de la précision de la comparaison. Si vous souhaitez comparer exactement le même nombre, choisissez simplement ==. (Vous ne voulez presque jamais faire cela à moins que vous ne vouliez réellement exactement le même nombre.) Sur n'importe quelle plate-forme décente, vous pouvez également faire ce qui suit:

diff= a - b; return fabs(diff)<EPSILON;

comme a fabstendance à être assez rapide. Par assez rapide, je veux dire que c'est essentiellement un bit ET, donc il vaut mieux être rapide.

Et les astuces entières pour comparer les doubles et les flottants sont agréables mais tendent à rendre plus difficile la gestion efficace des différents pipelines CPU. Et ce n'est certainement pas plus rapide sur certaines architectures en ordre de nos jours en raison de l'utilisation de la pile comme zone de stockage temporaire pour les valeurs fréquemment utilisées. (Load-hit-store pour ceux qui s'en soucient.)

Mat Noguchi
la source
0

En termes d'échelle de quantités:

Si epsilonest la petite fraction de la grandeur de la quantité (ie valeur relative) dans une certain sens physique et Aet Btypes est comparable dans le même sens, que je pense que ce qui suit est tout à fait correct:

#include <limits>
#include <iomanip>
#include <iostream>

#include <cmath>
#include <cstdlib>
#include <cassert>

template< typename A, typename B >
inline
bool close_enough(A const & a, B const & b,
                  typename std::common_type< A, B >::type const & epsilon)
{
    using std::isless;
    assert(isless(0, epsilon)); // epsilon is a part of the whole quantity
    assert(isless(epsilon, 1));
    using std::abs;
    auto const delta = abs(a - b);
    auto const x = abs(a);
    auto const y = abs(b);
    // comparable generally and |a - b| < eps * (|a| + |b|) / 2
    return isless(epsilon * y, x) && isless(epsilon * x, y) && isless((delta + delta) / (x + y), epsilon);
}

int main()
{
    std::cout << std::boolalpha << close_enough(0.9, 1.0, 0.1) << std::endl;
    std::cout << std::boolalpha << close_enough(1.0, 1.1, 0.1) << std::endl;
    std::cout << std::boolalpha << close_enough(1.1,    1.2,    0.01) << std::endl;
    std::cout << std::boolalpha << close_enough(1.0001, 1.0002, 0.01) << std::endl;
    std::cout << std::boolalpha << close_enough(1.0, 0.01, 0.1) << std::endl;
    return EXIT_SUCCESS;
}
Tomilov Anatoliy
la source
0

J'utilise ce code:

bool AlmostEqual(double v1, double v2)
    {
        return (std::fabs(v1 - v2) < std::fabs(std::min(v1, v2)) * std::numeric_limits<double>::epsilon());
    }
Chunde Huang
la source
2
Ce n'est pas pour ça epsilon.
Sneftel
1
Pourquoi pas? Pouvez-vous l'expliquer?
debuti
2
@debuti epsilonest simplement la distance entre 1 et le prochain nombre représentable après 1. Au mieux, ce code essaie juste de vérifier si les deux nombres sont exactement égaux l'un à l'autre, mais parce que les non-puissances de 2 sont multipliées par epsilon, il ne fait même pas cela correctement.
Sneftel
2
Oh et std::fabs(std::min(v1, v2)) c'est incorrect - pour les entrées négatives, il choisit celle avec la plus grande ampleur.
Sneftel
0

J'écris ceci pour Java, mais peut-être que vous le trouvez utile. Il utilise des longs au lieu de doubles, mais prend en charge les NaN, les sous-normales, etc.

public static boolean equal(double a, double b) {
    final long fm = 0xFFFFFFFFFFFFFL;       // fraction mask
    final long sm = 0x8000000000000000L;    // sign mask
    final long cm = 0x8000000000000L;       // most significant decimal bit mask
    long c = Double.doubleToLongBits(a), d = Double.doubleToLongBits(b);        
    int ea = (int) (c >> 52 & 2047), eb = (int) (d >> 52 & 2047);
    if (ea == 2047 && (c & fm) != 0 || eb == 2047 && (d & fm) != 0) return false;   // NaN 
    if (c == d) return true;                            // identical - fast check
    if (ea == 0 && eb == 0) return true;                // ±0 or subnormals
    if ((c & sm) != (d & sm)) return false;             // different signs
    if (abs(ea - eb) > 1) return false;                 // b > 2*a or a > 2*b
    d <<= 12; c <<= 12;
    if (ea < eb) c = c >> 1 | sm;
    else if (ea > eb) d = d >> 1 | sm;
    c -= d;
    return c < 65536 && c > -65536;     // don't use abs(), because:
    // There is a posibility c=0x8000000000000000 which cannot be converted to positive
}
public static boolean zero(double a) { return (Double.doubleToLongBits(a) >> 52 & 2047) < 3; }

Gardez à l'esprit qu'après un certain nombre d'opérations en virgule flottante, le nombre peut être très différent de ce que nous attendons. Il n'y a pas de code pour résoudre ce problème.

Caméléon
la source
0

Que dis-tu de ça?

template<typename T>
bool FloatingPointEqual( T a, T b ) { return !(a < b) && !(b < a); }

J'ai vu différentes approches - mais je n'ai jamais vu cela, donc je suis curieux d'entendre des commentaires aussi!

derke
la source
Cela ne fonctionne pas pour 1.99999999 et 1.99999998
Mehdi
@Mehdi Je viens d'essayer avec repl.it/repls/SvelteSimpleNumerator#main.cpp et il semble se comporter comme prévu - mais peut-être avez-vous une implémentation particulière du compilateur à référencer qui ne fait pas cela?
derke Il y a
-1
/// testing whether two doubles are almost equal. We consider two doubles
/// equal if the difference is within the range [0, epsilon).
///
/// epsilon: a positive number (supposed to be small)
///
/// if either x or y is 0, then we are comparing the absolute difference to
/// epsilon.
/// if both x and y are non-zero, then we are comparing the relative difference
/// to epsilon.
bool almost_equal(double x, double y, double epsilon)
{
    double diff = x - y;
    if (x != 0 && y != 0){
        diff = diff/y; 
    }

    if (diff < epsilon && -1.0*diff < epsilon){
        return true;
    }
    return false;
}

J'ai utilisé cette fonction pour mon petit projet et cela fonctionne, mais notez ce qui suit:

Une erreur de double précision peut vous surprendre. Disons que epsilon = 1.0e-6, alors 1.0 et 1.000001 ne doivent PAS être considérés comme égaux selon le code ci-dessus, mais sur ma machine, la fonction les considère comme égaux, c'est parce que 1.000001 ne peut pas être précisément traduit en format binaire, il s'agit probablement de 1.0000009xxx. Je le teste avec 1.0 et 1.0000011 et cette fois j'obtiens le résultat attendu.

Murphy78
la source
-1

Voici une autre solution avec lambda:

#include <cmath>
#include <limits>

auto Compare = [](float a, float b, float epsilon = std::numeric_limits<float>::epsilon()){ return (std::fabs(a - b) <= epsilon); };
Amir Saniyan
la source
C'est exactement la même chose que la plupart des autres réponses, sauf que c'est un lambda et n'a aucune explication, donc cela n'ajoute pas beaucoup de valeur comme réponse.
stijn
-2

Ma manière n'est peut-être pas correcte mais utile

Convertissez les deux flottants en chaînes, puis comparez les chaînes

bool IsFlaotEqual(float a, float b, int decimal)
{
    TCHAR form[50] = _T("");
    _stprintf(form, _T("%%.%df"), decimal);


    TCHAR a1[30] = _T(""), a2[30] = _T("");
    _stprintf(a1, form, a);
    _stprintf(a2, form, b);

    if( _tcscmp(a1, a2) == 0 )
        return true;

    return false;

}

la superposition d'opérateurs peut également être effectuée

Vijay
la source
+1: hé, je ne vais pas faire de programmation de jeu avec ça, mais l'idée de flotteurs de détournement est venue plusieurs fois dans le blog de Bruce Dawson (traité?: D) sur la question, et si vous êtes piégé dans une pièce et quelqu'un met un pistolet sur votre tête et dit "hé vous devez comparer deux flotteurs à X chiffres significatifs, vous avez 5 minutes, ALLEZ!" c'est probablement un à considérer. ;)
shelleybutterfly
@shelleybutterfly Là encore, la question était de trouver le moyen le plus efficace de comparer deux nombres à virgule flottante.
Tommy Andersen
@TommyA lol peut-être, mais je parie que les allers-retours ont été revus à la baisse pour des raisons non liées à l'efficacité. Bien que mon intuition soit que ce serait plutôt inefficace par rapport aux mathématiques HW fp, mais dit également que les algorithmes dans le logiciel fp sont peu susceptibles d'avoir une différence bigO au moins. J'attends avec impatience l'analyse que vous avez faite, montrant que les problèmes d'efficacité dans ce cas sont importants. En outre, parfois moins qu'optimale peut toujours être une réponse précieuse, et comme elle a été rejetée - malgré le fait qu'elle soit une technique valable qui a même été mentionnée par le blog de Dawson sur le sujet, j'ai donc pensé qu'elle méritait un vote positif.
shelleybutterfly
-2

Vous ne pouvez pas comparer deux doubleavec un fixe EPSILON. Selon la valeur de double, EPSILONvarie.

Une meilleure double comparaison serait:

bool same(double a, double b)
{
  return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b
    && std::nextafter(a, std::numeric_limits<double>::max()) >= b;
}
Daniel Laügt
la source
-2

De manière plus générique:

template <typename T>
bool compareNumber(const T& a, const T& b) {
    return std::abs(a - b) < std::numeric_limits<T>::epsilon();
}
beroso
la source
4
Cette méthode présente de nombreuses faiblesses, comme si les chiffres aet bsont déjà plus petits que epsilon()là, la différence peut encore être significative. Inversement, si les nombres sont très grands, même quelques bits d'erreur feront échouer la comparaison même si vous vouliez que les nombres soient considérés comme égaux. Cette réponse est exactement le type d'algorithme de comparaison "générique" que vous voulez éviter.
SirGuy
-3

Pourquoi ne pas effectuer XOR au niveau du bit? Deux nombres à virgule flottante sont égaux si leurs bits correspondants sont égaux. Je pense que la décision de placer les bits d'exposant avant la mantisse a été prise pour accélérer la comparaison de deux flotteurs. Je pense que de nombreuses réponses ici manquent le point de comparaison epsilon. La valeur d'Epsilon dépend uniquement de la précision avec laquelle les nombres à virgule flottante sont comparés. Par exemple, après avoir fait de l'arithmétique avec des flottants, vous obtenez deux nombres: 2,5642943554342 et 2,5642943554345. Ils ne sont pas égaux, mais pour la solution, seuls 3 chiffres décimaux comptent donc ils sont égaux: 2,564 et 2,564. Dans ce cas, vous choisissez epsilon égal à 0,001. La comparaison d'Epsilon est également possible avec XOR au niveau du bit. Corrigez-moi si je me trompe.

RadioTransmission
la source
Veuillez ne pas ajouter la même réponse à plusieurs questions. Répondez à la meilleure et marquez les autres comme des doublons. Voir meta.stackexchange.com/questions/104227/…
Bhargav Rao
Je ne pense pas que la "comparaison epsilon" soit possible en utilisant uniquement ExOr (et une ou deux comparaisons), même limitée aux représentations normalisées dans le même format.
greybeard