63 votes

Ajout du plus petit flottant possible à un flotteur

Je veux ajouter la plus petite valeur possible d'un float à un float. Donc, par exemple, j'ai essayé de faire cela pour obtenir 1.0 + le plus petit float possible:

 float result = 1.0f + std::numeric_limits<float>::min();
 

Mais après cela, j'obtiens les résultats suivants:

 (result > 1.0f) == false
(result == 1.0f) == true
 

J'utilise Visual Studio 2015. Pourquoi cela se produit-il? Que puis-je faire pour le contourner?

89voto

Benjamin Lindley Points 51005

Si vous voulez que le prochain représentable valeur après le 1, il y a une fonction pour ça, appelé std::nextafter, à partir de l' <cmath> - tête.

float result = std::nextafter(1.0f, 2.0f);

Il retourne la prochaine représentable de départ de la valeur du premier argument dans le sens de la deuxième argument. Donc si vous voulez trouver la prochaine valeur inférieure à 1, vous pourriez faire ceci:

float result = std::nextafter(1.0f, 0.0f);

L'ajout le plus petit positif représentable valeur 1 ne fonctionne pas parce que la différence entre le 1 et le prochain représentable valeur est supérieure à la différence entre le 0 et la prochaine valeur représentable.

43voto

6502 Points 42700

Le "problème" que nous observons est en raison de la nature même de l'arithmétique à virgule flottante.

Dans la FP la précision dépend de l'échelle; autour de la valeur 1.0 la précision n'est pas suffisante pour être en mesure de différencier 1.0 et 1.0+min_representablemin_representable est la plus petite valeur supérieure à zéro (même si nous considérons seulement le plus petit numéro normalisé, std::numeric_limits<float>::min()... le plus petit des nombres dénormalisés est un autre quelques ordres de grandeur plus faible).

Par exemple avec double précision 64 bits IEEE754 des nombres à virgule flottante, autour de l'échelle de l' x=10000000000000000 (10à 16), il est impossible de distinguer x et x+1.


Le fait que le changement de résolution à l'échelle est la raison pour laquelle le nom "virgule flottante", parce que le point décimal "flotte". Une représentation en virgule fixe à la place aura une résolution fixe (par exemple avec 16 chiffres binaires ci-dessous les unités que vous avez une précision de 1/65536 ~ 0.00001).

Par exemple, dans le IEEE754 32 bits à virgule flottante au format il y a un bit pour le signe, 8 bits pour l'exposant et le 31 bits pour la mantisse:

floating point


La plus petite valeur eps tels que 1.0f + eps != 1.0f est disponible en pré-défini, constante en FLT_EPSILONou std::numeric_limits<float>::epsilon. Voir aussi la machine epsilon sur Wikipédia, qui explique comment epsilon se rapporte à des erreurs d'arrondi.

I. e. epsilon est la plus petite valeur qui fait ce que vous attendiez ici, de faire une différence lorsqu'il est ajouté à 1.0.

Le plus général version de ce (pour les autres nombres de 1.0) est appelée de 1 unité à la dernière place (de la mantisse). Voir Wikipedia ULP article.

20voto

Matteo Italia Points 53117

min est la plus petite valeur non nulle (normalisé) flotteur peut supposer, c'est à dire quelque chose autour de 2-126 (-126 est le minimum autorisé l'exposant d'un float); maintenant, si vous somme à 1, vous aurez toujours 1, depuis un float a seulement 23 bits de mantisse, si ces un petit changement ne peut pas être représenté dans un "grand" nombre (vous avez besoin d'une 126 bits de mantisse de voir un changement en additionnant 2-126 1).

Le minimum possible de changer à 1, au lieu de cela, est - epsilon (la soi-disant machine epsilon), qui est en fait 2-23 - , car il affecte le dernier bit de la mantisse.

4voto

Johan Lundberg Points 5835

Pour augmenter / réduire une valeur en virgule flottante du montant le plus petit possible, utilisez nextafter vers +/ infinity() .

Si vous n'utilisez que next_after(x,std::numeric_limits::max()) , le résultat sera faux dans le cas où x est infini.

 #include <iostream>
#include <limits>
#include <cmath>

template<typename T>
T next_above(const T& v){
    return std::nextafter(v,std::numeric_limits<T>::infinity()) ;
}
template<typename T>
T next_below(const T& v){
    return std::nextafter(v,-std::numeric_limits<T>::infinity()) ;
}

int main(){
  std::cout << "eps   : "<<std::numeric_limits<double>::epsilon()<< std::endl; // gives eps

  std::cout << "after : "<<next_above(1.0) - 1.0<< std::endl; // gives eps (the definition of eps)
  std::cout << "below : "<<next_below(1.0) - 1.0<< std::endl; // gives -eps/2

  // Note: this is what next_above does:
  std::cout << std::nextafter(std::numeric_limits<double>::infinity(),
     std::numeric_limits<double>::infinity()) << std::endl; // gives inf

  // while this is probably not what you need:
  std::cout << std::nextafter(std::numeric_limits<double>::infinity(),
     std::numeric_limits<double>::max()) << std::endl; // gives 1.79769e+308

}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X