Comparer le double au zéro en utilisant epsilon

Aujourd'hui, je regardais à travers du code c++ (écrit par quelqu'un d'autre) et j'ai trouvé cette section:

double someValue = ...
if (someValue <  std::numeric_limits<double>::epsilon() && 
    someValue > -std::numeric_limits<double>::epsilon()) {
  someValue = 0.0;
}

j'essaie de comprendre si ça a un sens.

la documentation pour epsilon() dit:

la fonction renvoie la différence entre 1 et la plus petite valeur supérieure à 1 qui peut être représentée [par un double].

cela s' 0 ainsi, c'est à dire epsilon() est la plus petite valeur supérieure à 0? Ou y a-t-il des nombres entre 0 et 0 + epsilon qui peuvent être représentés par un double ?

dans la négative, la comparaison n'est-elle pas équivalente à someValue == 0.0 ?

197
demandé sur Sebastian Krysmanski 2012-12-04 12:41:30

11 réponses

en supposant un double IEEE 64 bits, il y a un mantissa 52 bits et un exposant 11 bits. Regardez les chiffres suivants:

1.0000 00000000 00000000 00000000 00000000 00000000 00000000 × 2^0 = 1

le plus petit nombre représentatif supérieur à 1: 151960920"

1.0000 00000000 00000000 00000000 00000000 00000000 00000001 × 2^0 = 1 + 2^-52

donc:

epsilon = (1 + 2^-52) - 1 = 2^-52

y a-t-il des nombres entre 0 et epsilon? Beaucoup... Par exemple: le nombre minimal représentatif positif (normal) est:

1.0000 00000000 00000000 00000000 00000000 00000000 00000000 × 2^-1022 = 2^-1022

en fait, il ya environ (1022 - 52 + 1)×2^52 = 4372995238176751616 entre 0 et epsilon, ce qui représente environ 47% de tous les nombres représentatifs positifs...

182
répondu ybungalobill 2012-12-04 09:25:13

le test n'est certainement pas le même que someValue == 0 . L'idée de nombres à virgule flottante, c'est qu'ils stocker un exposant et un significande. Ils représentent donc une valeur avec un certain nombre de chiffres binaires significatifs de précision (53 dans le cas d'un double IEEE). Les valeurs représentatives sont beaucoup plus denses près de 0 qu'elles ne sont près de 1.

pour utiliser un système décimal plus familier, supposons que vous stockez une valeur décimale " à 4 significatif chiffres" en exposant. Ensuite, la valeur suivante représentant plus grande que 1 est 1.001 * 10^0 , et epsilon est 1.000 * 10^-3 . Mais 1.000 * 10^-4 est aussi représentatif, en supposant que l'exposant peut stocker -4. Vous pouvez me croire sur parole qu'un double IEEE peut stocker des exposants moins que l'exposant de epsilon .

vous ne pouvez pas dire à partir de ce code seul s'il est raisonnable ou non d'utiliser epsilon spécifiquement comme le lié, vous avez besoin de regarder le contexte. Il se peut que epsilon soit une estimation raisonnable de l'erreur dans le calcul qui a produit someValue , et il se peut que ce ne soit pas le cas.

17
répondu Steve Jessop 2012-12-04 09:26:57

il y a des nombres qui existent entre 0 et epsilon parce que epsilon est la différence entre 1 et le nombre le plus élevé suivant qui peut être représenté au-dessus de 1 et non la différence entre 0 et le nombre le plus élevé suivant qui peut être représenté au-dessus de 0 (si c'était le cas, ce code ferait très peu): -

#include <limits>

int main ()
{
  struct Doubles
  {
      double one;
      double epsilon;
      double half_epsilon;
  } values;

  values.one = 1.0;
  values.epsilon = std::numeric_limits<double>::epsilon();
  values.half_epsilon = values.epsilon / 2.0;
}

en utilisant un débogueur, arrêtez le programme à la fin de main et regardez les résultats et vous verrez que epsilon / 2 est distinct d'epsilon, zéro et un.

ainsi cette fonction prend des valeurs entre +/- epsilon et les rend zéro.

12
répondu Skizz 2012-12-04 09:11:52

aproximation d'epsilon (plus petite différence possible) autour d'un nombre (1,0, 0,0, ...) peut être imprimé avec le programme suivant. Il affiche la sortie suivante:

epsilon for 0.0 is 4.940656e-324

epsilon for 1.0 is 2.220446e-16

Un peu de réflexion le rend clair, que l'epsilon devient plus petit le plus petit le nombre est nous utilisons pour regarder sa valeur epsilon, parce que l'exposant peut s'ajuster à la taille de ce nombre.

#include <stdio.h>
#include <assert.h>
double getEps (double m) {
  double approx=1.0;
  double lastApprox=0.0;
  while (m+approx!=m) {
    lastApprox=approx;
    approx/=2.0;
  }
  assert (lastApprox!=0);
  return lastApprox;
}
int main () {
  printf ("epsilon for 0.0 is %e\n", getEps (0.0));
  printf ("epsilon for 1.0 is %e\n", getEps (1.0));
  return 0;
}
5
répondu pbhd 2012-12-04 13:41:41

supposons que nous travaillions avec des numéros de point flottants jouets qui s'inscrivent dans un registre de 16 bits. Il y a un bit de signe, un exposant de 5 bits, et un mantissa de 10 bits.

la valeur de ce nombre de virgule flottant est le mantissa, interprété comme une valeur décimale binaire, multiplié par deux à la puissance de l'exposant.

autour de 1 l'exposant est égal à zéro. Ainsi le plus petit chiffre du mantissa est une partie en 1024.

près de la moitié de l'exposant est moins un, donc la plus petite partie du mantissa est moitié plus grande. Avec un exposant de cinq bits il peut atteindre négatif 16, à quel point la plus petite partie de la mantissa vaut une partie en 32m. Et à négatif 16 exposant, la valeur est autour d'une partie en 32k, beaucoup plus proche de zéro que l'epsilon autour d'un que nous avons calculé ci-dessus!

maintenant c'est un modèle de pointe flottante jouet qui ne reflète pas toutes les bizarreries d'un système de pointe flottante réel , mais la capacité de réfléchir les valeurs inférieures à epsilon sont raisonnablement similaires avec les valeurs réelles en virgule flottante.

3
répondu Yakk - Adam Nevraumont 2014-05-28 22:51:10

je pense que cela dépend de la précision de votre ordinateur. Jetez un coup d'oeil à cette table : vous pouvez voir que si votre epsilon est représenté par le double, mais votre précision est plus élevée, la comparaison n'est pas équivalente à

someValue == 0.0

bonne question quand même!

2
répondu Luca Davanzo 2012-12-04 08:57:04

vous ne pouvez pas appliquer ceci à 0, à cause de mantissa et des parties exposant. Grâce à exposant vous pouvez stocker très peu de nombres, qui sont plus petits que epsilon, mais lorsque vous essayez de faire quelque chose comme (1.0 - "très petit nombre"), vous obtiendrez 1.0. Epsilon est un indicateur non de valeur, mais de précision de valeur, qui est en mantissa. Il montre combien de chiffres décimaux conséquents corrects de nombre nous pouvons stocker.

2
répondu Arsenii Fomin 2012-12-04 08:57:11

La différence entre X et la valeur X varie en fonction de X .

epsilon() n'est que la différence entre 1 et la valeur 1 .

La différence entre 0 et la valeur 0 n'est pas epsilon() .

à la place, vous pouvez utiliser std::nextafter pour comparer une valeur double avec 0 comme suit:

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

double someValue = ...
if (same (someValue, 0.0)) {
  someValue = 0.0;
}
2
répondu Daniel Laügt 2016-02-06 18:29:34

donc disons que le système ne peut pas distinguer 1.00000000000000000000000 et 1.0000000000000000001. c'est 1.0 et 1.0 + 1e-20. Pensez vous qu'il y a encore quelques valeurs qui peuvent être représentés entre -1e-20 et +1e-20?

1
répondu cababunga 2012-12-04 08:50:36

avec le point flottant IEEE, entre la plus petite valeur positive non nulle et la plus petite valeur négative non nulle, il existe deux valeurs: zéro positif et zéro négatif. Tester si une valeur se situe entre les plus petites valeurs non nulles équivaut à tester l'égalité avec zéro; l'assignation, cependant, peut avoir un effet, car il changerait un zéro négatif à un zéro positif.

il serait concevable qu'un format à virgule flottante puisse avoir trois valeurs entre les plus petites valeurs finies positives et négatives: infinitésimal positif, zéro non signé, et infinitésimal négatif. Je ne suis pas familier avec les formats à virgule flottante qui fonctionnent en fait de cette façon, mais un tel comportement serait parfaitement raisonnable et sans doute meilleur que celui de L'IEEE (peut-être pas assez mieux pour être intéressant d'ajouter du matériel supplémentaire pour le supporter, mais mathématiquement 1/(1/INF), 1/(-1/INF), et 1/(1-1) devrait représenter trois cas distincts illustrant trois différents des zéros). Je ne sais pas si une norme C imposerait que les infinitésimaux signés, s'ils existent, devraient être comparables à zéro. Si ce n'est pas le cas, un code comme celui-ci pourrait assurer utilement que, par exemple, diviser un nombre de façon répétée par deux conduirait finalement à zéro plutôt que d'être collé sur "infinitésimal".

1
répondu supercat 2012-12-04 16:05:49

aussi, une bonne raison pour avoir une telle fonction est d'enlever "denormals" (ces très petits nombres qui ne peuvent plus utiliser le "1" principal implicite et ont une représentation spéciale FP). Pourquoi voudriez-vous faire cela? Parce que certaines machines (en particulier, certaines Pentium 4S plus anciennes) deviennent vraiment, vraiment lents lors du traitement des denormaux. D'autres juste obtenir un peu plus lente. Si votre application n'a pas vraiment besoin de ces très petits nombres, les jeter à zéro est un bon solution. Les bons endroits pour considérer ceci sont les dernières étapes de n'importe quels filtres IIR ou fonctions de décomposition.

Voir aussi: Pourquoi changer de 0,1 f 0 ralentir les performances en 10x?

et http://en.wikipedia.org/wiki/Denormal_number

0
répondu Dithermaster 2017-05-23 12:18:09