web-dev-qa-db-fra.com

Comment comparer correctement et uniformément les flotteurs?

Chaque fois que je démarre un nouveau projet et quand j'ai besoin de comparer des variables flottantes ou doubles, j'écris le code comme celui-ci:

if (fabs(prev.min[i] - cur->min[i]) < 0.000001 &&
    fabs(prev.max[i] - cur->max[i]) < 0.000001) {
        continue;
}

Ensuite, je veux me débarrasser de ces variables magiques 0.000001 (et 0.00000000001 pour double) et fabs, alors j'écris une fonction en ligne et certaines définissent:

#define FLOAT_TOL 0.000001

Je me demande donc s'il existe un moyen standard de procéder? Peut-être un fichier d'en-tête standard? Ce serait aussi bien d'avoir des flottants et des doubles limites (valeurs min et max)

29
Dmitriy

Merci pour vos réponses, ils m'ont beaucoup aidé. J'ai lu ces documents: premier et second

La réponse est d'utiliser ma propre fonction pour une comparaison relative:

bool areEqualRel(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon * std::max(fabs(a), fabs(b)));
}

C'est la solution la plus adaptée à mes besoins. Cependant, j'ai écrit quelques tests et autres méthodes de comparaison. J'espère que cela sera utile à quelqu'un. areEqualRel passe ces tests, d'autres non.

#include <iostream>
#include <limits>
#include <algorithm>

using std::cout;
using std::max;

bool areEqualAbs(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon);
}

bool areEqual(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon * std::max(1.0f, std::max(a, b)));
}

bool areEqualRel(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon * std::max(fabs(a), fabs(b)));
}

int main(int argc, char *argv[])
{
    cout << "minimum: " << FLT_MIN      << "\n";
    cout << "maximum: " << FLT_MAX      << "\n";
    cout << "epsilon: " << FLT_EPSILON  << "\n";

    float a = 0.0000001f;
    float b = 0.0000002f;
    if (areEqualRel(a, b, FLT_EPSILON)) {
        cout << "are equal a: " << a << " b: " << b << "\n";
    }
    a = 1000001.f;
    b = 1000002.f;
    if (areEqualRel(a, b, FLT_EPSILON)) {
        cout << "are equal a: " << a << " b: " << b << "\n";
    }
}
5
Dmitriy

De The Floating-Point Guide :

C'est une mauvaise façon de le faire, car un epsilon fixe choisi parce qu'il "semble petit" pourrait en fait être beaucoup trop grand lorsque les nombres comparés sont également très petits. La comparaison retournerait "vrai" pour des nombres assez différents. Et lorsque les nombres sont très grands, l'epsilon pourrait finir par être plus petit que la plus petite erreur d'arrondi, de sorte que la comparaison retourne toujours "faux".

Le problème avec le "nombre magique" ici n'est pas qu'il est codé en dur mais qu'il est "magique": vous n'aviez pas vraiment de raison de choisir 0.000001 plutôt que 0.000005 ou 0.0000000000001, n'est-ce pas? Notez que float peut représenter approximativement ces dernières et des valeurs encore plus petites - c'est juste environ 7 décimales de précision après le premier chiffre différent de zéro!

Si vous allez utiliser un epsilon fixe, vous devez vraiment le choisir en fonction des exigences du morceau de code particulier où vous l'utilisez. L'alternative est d'utiliser une marge d'erreur relative (voir le lien en haut pour plus de détails) ou, mieux encore, ou comparer les flottants sous forme d'entiers .

17
Michael Borgwardt

La norme fournit une valeur epsilon. C'est dedans <limits> et vous pouvez accéder à la valeur par std::numeric_limits<float>::epsilon et std::numeric_limits<double>::epsilon. Il y a d'autres valeurs là-dedans, mais je n'ai pas vérifié ce qu'est exactement.

12
Puppy

Vous pouvez utiliser std::nextafter pour tester deux double avec le plus petit epsilon sur une valeur (ou un facteur du plus petit epsilon).

bool nearly_equal(double a, double b)
{
  return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b
    && std::nextafter(a, std::numeric_limits<double>::max()) >= b;
}

bool nearly_equal(double a, double b, int factor /* a factor of epsilon */)
{
  double min_a = a - (a - std::nextafter(a, std::numeric_limits<double>::lowest())) * factor;
  double max_a = a + (std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor;

  return min_a <= b && max_a >= b;
}
7
Daniel Laügt

Vous devez savoir que si vous comparez deux flotteurs pour l'égalité, vous faites intrinsèquement la mauvaise chose. Ajouter un facteur de pente à la comparaison n'est pas suffisant.

7
ddyer

Vous devez utiliser la définition standard dans float.h:

#define DBL_EPSILON     2.2204460492503131e-016 /* smallest float value such that 1.0+DBL_EPSILON != 1.0 */

ou la classe numeric_limits:

// excerpt
template<>
class numeric_limits<float> : public _Num_float_base
{
public:
    typedef float T;

    // return minimum value
    static T (min)() throw();

    // return smallest effective increment from 1.0
    static T epsilon() throw();

    // return largest rounding error
    static T round_error() throw();

    // return minimum denormalized value
     static T denorm_min() throw();
};

[EDIT: Rendu un peu plus lisible.]

Mais en plus, cela dépend de ce que vous recherchez.

4
0xbadf00d

Voici une implémentation c ++ 11 de la solution de @geotavros. Il utilise la nouvelle fonction std::numeric_limits<T>::epsilon() et le fait que std::fabs() et std::fmax() ont maintenant des surcharges pour float, double et long float.

template<typename T>
static bool AreEqual(T f1, T f2) { 
  return (std::fabs(f1 - f2) <= std::numeric_limits<T>::epsilon() * std::fmax(std::fabs(f1), std::fabs(f2)));
}
3
Jelle van Campen

Cet article a une explication complète de la façon de comparer les nombres à virgule flottante: http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/

Extrait:

  • Si vous comparez avec zéro, les comparaisons basées sur les epsilons relatifs et les ULP sont généralement dénuées de sens. Vous devrez utiliser un epsilon absolu, dont la valeur peut être un petit multiple de FLT_EPSILON et les entrées de votre calcul. Peut être.
  • Si vous comparez avec un nombre non nul, les comparaisons basées sur des epsilons relatifs ou des ULP sont probablement ce que vous voulez. Vous voudrez probablement un petit multiple de FLT_EPSILON pour votre epsilon relatif, ou un petit nombre d'ULP. Un epsilon absolu pourrait être utilisé si vous saviez exactement à quel nombre vous compariez.
1
blufiro