web-dev-qa-db-fra.com

Comparer le nombre à virgule flottante à zéro

C++ FAQ lite "[29.17] Pourquoi ma comparaison en virgule flottante ne fonctionne-t-elle pas?" recommande ce test d'égalité:

#include <cmath>  /* for std::abs(double) */

inline bool isEqual(double x, double y)
{
  const double epsilon = /* some small number such as 1e-5 */;
  return std::abs(x - y) <= epsilon * std::abs(x);
  // see Knuth section 4.2.2 pages 217-218
}
  1. Est-il exact que cela implique que les seuls nombres égaux à zéro sont +0 et -0?
  2. Doit-on utiliser cette fonction aussi lors du test de zéro ou plutôt d’un test comme |x| < epsilon?

Mettre à jour

Comme l'a souligné Daniel Daranas, la fonction devrait probablement s'appeler isNearlyEqual (ce qui est mon cas).

Quelqu'un a souligné ce lien , que je souhaite partager de manière plus visible.

45
Micha Wiedenmann

Vous avez raison avec votre observation. 

Si x == 0.0, alors abs(x) * epsilon est égal à zéro et vous testez si abs(y) <= 0.0.

Si y == 0.0, vous testez abs(x) <= abs(x) * epsilon, ce qui signifie soit epsilon >= 1 (ce n’est pas) ou x == 0.0.

Donc, soit is_equal(val, 0.0) ou is_equal(0.0, val) serait inutile, et vous pourriez simplement dire val == 0.0. Si vous souhaitez uniquement accepter exactement+0.0 et -0.0.

La recommandation de la FAQ dans ce cas est d'une utilité limitée. Il n'y a pas de "taille unique" comparaison en virgule flottante. Vous devez penser à la sémantique de vos variables, à la plage de valeurs acceptable et à l’ampleur de l’erreur introduite par vos calculs. Même la FAQ mentionne une mise en garde, affirmant que cette fonction n'est généralement pas un problème "lorsque les magnitudes de x et y sont nettement supérieures à celles d'epsilon, mais que votre kilométrage peut varier".

35
Ben Voigt

Non.

L'égalité est l'égalité.

La fonction que vous avez écrite ne testera pas deux doublons pour l’égalité, comme son nom le promet. Il ne testera que si deux doubles sont "assez proches" l'un de l'autre.

Si vous vraiment voulez tester deux doubles pour l’égalité, utilisez celui-ci:

inline bool isEqual(double x, double y)
{
   return x == y;
}

Les normes de codage recommandent généralement de ne pas comparer deux doubles pour une égalité exacte. Mais c'est un sujet différent. Si vous réellement voulez comparer deux doubles pour une égalité exacte, x == y est le code que vous voulez.

10.00000000000000001 n'est pas égal à 10.0, peu importe ce qu'ils vous disent.

Un exemple d'utilisation d'égalité exacte consiste à utiliser une valeur particulière d'un double comme synonyme d'un état particulier, tel que "en attente de calcul" ou "aucune donnée disponible". Cela n'est possible que si les valeurs numériques effectives après ce calcul en attente ne sont qu'un sous-ensemble des valeurs possibles d'un double. Le cas particulier le plus typique est lorsque cette valeur est non négative et que vous utilisez -1.0 en tant que représentation (exacte) d'un "calcul en attente" ou de "aucune donnée disponible". Vous pouvez représenter cela avec une constante:

const double NO_DATA = -1.0;

double myData = getSomeDataWhichIsAlwaysNonNegative(someParameters);

if (myData != NO_DATA)
{
    ...
}
17
Daniel Daranas

Vous pouvez utiliser std::nextafter avec une factor fixe de la epsilon d'une valeur comme celle-ci:

bool isNearlyEqual(double a, double b)
{
  int factor = /* a fixed 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;
}
6
Daniel Laügt

2 + 2 = 5 (*)

(pour certaines valeurs de précision flottante de 2)

Ce problème se pose fréquemment lorsque nous pensons que la "virgule flottante" est un moyen d’accroître la précision. Ensuite, nous nous heurtons à la partie "flottante", ce qui signifie qu'il n'y a aucune garantie de quels nombres peuvent être représentés.

Ainsi, bien que nous puissions facilement représenter "1.0, -1.0, 0.1, -0.1" à mesure que nous atteignons des nombres plus grands, nous commençons à voir des approximations - ou nous devrions, sauf que nous les cachons souvent en tronquant les nombres pour les afficher.

En conséquence, nous pourrions penser que l’ordinateur stocke "0.003" mais il peut au contraire stocker "0.0033333333334".

Qu'est-ce qui se passe si vous effectuez "0.0003 - 0.0002"? Nous nous attendons à 0,0001, mais les valeurs actuellement stockées pourraient ressembler davantage à "0,00033" - "0,00029", ce qui donnerait "0,000004", ou la valeur représentable la plus proche, qui pourrait être 0, ou "0.000006" .

Avec les opérations mathématiques en virgule flottante actuelles, il n'est pas garanti que (a/b) * b == a .

#include <stdio.h>

// defeat inline optimizations of 'a / b * b' to 'a'
extern double bodge(int base, int divisor) {
    return static_cast<double>(base) / static_cast<double>(divisor);
}

int main() {
    int errors = 0;
    for (int b = 1; b < 100; ++b) {
        for (int d = 1; d < 100; ++d) {
            // b / d * d ... should == b
            double res = bodge(b, d) * static_cast<double>(d);
            // but it doesn't always
            if (res != static_cast<double>(b))
                ++errors;
        }
    }
    printf("errors: %d\n", errors);
}

ideone signale 599 occurrences où (b * d)/d! = b utilise uniquement les 10 000 combinaisons de 1 <= b <= 100 et 1 <= d <= 100.

La solution décrite dans la FAQ consiste essentiellement à appliquer une contrainte de granularité: tester if (a == b +/- epsilon).

Une approche alternative consiste à éviter entièrement le problème en utilisant une précision de point fixe ou en utilisant la granularité souhaitée comme unité de base pour votre stockage. Par exemple. Si vous souhaitez que les temps soient stockés avec une précision de l'ordre de la nanoseconde, utilisez nanosecondes comme unité de stockage.

C++ 11 a introduit std :: ratio comme base pour les conversions en points fixes entre différentes unités de temps.

4
kfsone

Comme @Exceptyon l'a fait remarquer, cette fonction est «relative» aux valeurs que vous comparez. La mesure Epsilon * abs(x) sera mise à l'échelle en fonction de la valeur de x, afin d'obtenir un résultat de comparaison aussi précis que epsilon, quelle que soit la plage de valeurs en x ou y. 

Si vous comparez zéro (y) à une autre très petite valeur (x), disons 1e-8, abs(x-y) = 1e-8 sera toujours beaucoup plus grand que epsilon *abs(x) = 1e-13. Donc, sauf si vous avez affaire à un nombre extrêmement petit qui ne peut pas être représenté dans un type double, cette fonction devrait faire l'affaire et ne fera correspondre le zéro que pour +0 et -0.

La fonction semble parfaitement valide pour une comparaison à zéro. Si vous envisagez de l'utiliser, je vous suggère de l'utiliser partout où des flotteurs sont impliqués, sans avoir de cas particulier pour des choses comme zéro, simplement pour que le code soit uniforme.

ps: C'est une fonction soignée. Merci de nous l'avoir signalé.

1
Arun R

La comparaison simple de FP numéros a ses propres spécificités et la clé est la compréhension du format FP (voir https://en.wikipedia.org/wiki/IEEE_floating_point )

Lorsque FP les nombres sont calculés différemment, l'un par l'intermédiaire de sin (), bien que exp (), l'égalité stricte ne fonctionne pas, même si, mathématiquement, les nombres peuvent être égaux. La même manière ne fonctionnera pas l'égalité avec la constante. En fait, dans beaucoup de situations, les nombres FP ne doivent pas être comparés avec une égalité stricte (==)

Dans ce cas, utilisez la constante DBL_EPSIPON, qui est la valeur minimale ne modifiez pas la représentation de 1.0 ajoutée au nombre supérieur à 1.0. Pour les nombres à virgule flottante supérieurs à 2.0, DBL_EPSIPON n'existe pas du tout. Pendant ce temps, DBL_EPSILON a l'exposant -16, ce qui signifie que tous les nombres, disons, avec l'exposant -34, seraient absolument égaux par rapport à DBL_EPSILON.

Voir aussi exemple , pourquoi 10.0 == 10.0000000000000001

La comparaison de deux nombres en virgule flottante dépend de la nature de ces nombres, nous devrions calculer DBL_EPSILON pour eux, ce qui aurait un sens pour la comparaison. Simplement, nous devrions multiplier DBL_EPSILON par l’un de ces nombres. Lequel d'entre eux? Maximum de cours

bool close_enough(double a, double b){
    if (fabs(a - b) <= DBL_EPSILON * std::fmax(fabs(a), fabs(b)))
    {
        return true;
    }
    return false;
}

Tous les autres moyens vous donneraient des bugs avec des inégalités qui pourraient être très difficiles à attraper

1
Yuri S. Cherkasov

remarquez, ce code est:

std::abs((x - y)/x) <= epsilon

vous exigez que "l'erreur relative" sur la variable soit <= epsilon, et non que la différence absolue soit

0
Exceptyon