[c++] Quel est le moyen le plus efficace pour la comparaison flottante et double?



Answers

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

Vous devriez changer votre implémentation un peu:

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

Edit: Christer a ajouté une pile de super informations sur ce sujet sur un blog récent . Prendre plaisir.

Question

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

Simplement faire ceci 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 sait-il un smarter float plus intelligent?




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 pourriez vouloir essayer les deux. Si elles sont équivalentes en moyenne, prenez la mise en œuvre avec des fabs.

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

Enfin, vous pourriez obtenir un meilleur résultat en insérant cette fonction. Peu susceptible d'améliorer beaucoup si ...

Edit: JO, merci de corriger votre code. J'ai effacé mon commentaire en conséquence




General-purpose comparison of floating-point numbers is generally meaningless. How to compare really depends on a problem at hand. In many problems, numbers are sufficiently discretized to allow comparing them within a given tolerance. Unfortunately, there are just as many problems, where such trick doesn't really work. For one example, consider working with a Heaviside (step) function of a number in question (digital stock options come to mind) when your observations are very close to the barrier. Performing tolerance-based comparison wouldn't do much good, as it would effectively shift the issue from the original barrier to two new ones. Again, there is no general-purpose solution for such problems and the particular solution might require going as far as changing the numerical method in order to achieve stability.




In a more generic way:

template <typename T>
bool compareNumber(const T& a, const T& b) {
    return std::abs(a - b) < std::numeric_limits<T>::epsilon();
}



La manière 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();
}



There are actually cases in numerical software where you want to check whether two floating point numbers are exactly equal. I posted this on a similar question

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

So you can not say that "CompareDoubles1" is wrong in general.




In terms of the scale of quantities:

If epsilon is the small fraction of the magnitude of quantity (ie relative value) in some certain physical sense and A and B types is comparable in the same sense, than I think, that the following is quite 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;
}



Comme d'autres l'ont souligné, l'utilisation d'un epsilon à exposant fixe (tel que 0.0000001) 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 pas de valeurs à virgule flottante NO 32 bits entre ces deux nombres - 10000 et 10000.000977 sont aussi proches que possible sans être identiques pour un bit à bit. Ici, un epsilon de moins de 0,0009 est dénué de sens; vous pourriez aussi bien utiliser l'opérateur d'égalité direct.

De même, lorsque les deux valeurs s'approchent d'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 fonctionnera uniquement 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 tirez un numéro hors de l'air ("Hey! 0.00001 est petit, donc ça doit être bon!"), Vous êtes voué à 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 un autre cas de test.

Si vous faites de la programmation numérique de quelque nature que ce soit et que vous croyez avoir besoin d'atteindre des epsilons à virgule fixe, LIRE L'ARTICLE DE BRUCE SUR LA COMPARAISON DES NUMÉROS DE POINTS FLOTTANTS .

Comparaison des nombres à virgule flottante




La comparaison des nombres à virgule flottante dépend du contexte. Puisque même changer l'ordre des opérations peut produire des résultats différents, il est important de savoir à quel point vous voulez que les chiffres soient «égaux».

Comparer les nombres à virgule flottante par Bruce Dawson est un bon point de départ pour la comparaison à virgule flottante.

Les définitions suivantes proviennent de L'art de la programmation informatique de 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 à quel point vous voulez que les chiffres soient égaux.

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




My way may not be correct but useful

Convert both float to strings and then do string compare

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;

}

operator overlaoding can also be done




My class based on previously posted answers. Very similar to Google's code but I use a bias which pushes all NaN values above 0xFF000000. That allows a faster check for NaN.

This code is meant to demonstrate the concept, not be a general solution. Google's code already shows how to compute all the platform specific values and I didn't want to duplicate all that. I've done limited testing on this 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 );
                  }
};



I use this code:

bool AlmostEqual(double v1, double v2)
    {
        return (std::fabs(v1 - v2) < std::fabs(std::min(v1, v2)) * std::numeric_limits<double>::epsilon());
    }



Links