web-dev-qa-db-fra.com

Ajouter le plus petit flotteur possible à un flotteur

Je veux ajouter la plus petite valeur possible d'un flotteur à un flotteur. Ainsi, par exemple, j'ai essayé de faire ceci pour obtenir 1.0 + le plus petit flotteur 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?

59
Squidy

Si vous voulez la prochaine valeur représentable après 1, il existe une fonction pour celle appelée std::nextafter , à partir du <cmath> entête.

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

Il renvoie la prochaine valeur représentable à partir du premier argument dans le sens du deuxième argument. Donc, si vous souhaitez trouver la valeur suivante en dessous de 1, vous pouvez le faire:

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

L'ajout de la plus petite valeur représentable positive à 1 ne fonctionne pas car la différence entre 1 et la prochaine valeur représentable est supérieure à la différence entre 0 et la prochaine valeur représentable.

86
Benjamin Lindley

Le "problème" que vous observez est dû à la nature même de l'arithmétique à virgule flottante.

En FP la précision dépend de l'échelle; autour de la valeur 1.0 La précision n'est pas suffisante pour pouvoir faire la différence entre 1.0 Et 1.0+min_representablemin_representable est la plus petite valeur possible supérieure à zéro (même si nous ne considérons que le plus petit nombre normalisé, std::numeric_limits<float>::min()... le plus petit dénormal est de quelques ordres de grandeur plus petit).

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


Le fait que la résolution change avec l'échelle est la raison même du nom "virgule flottante", car la virgule décimale "flotte". Une représentation à virgule fixe aura à la place une résolution fixe (par exemple, avec 16 chiffres binaires en dessous des unités, vous avez une précision de 1/65536 ~ 0,00001).

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

floating point


La plus petite valeur eps telle que 1.0f + eps != 1.0f Est disponible en tant que constante prédéfinie comme FLT_EPSILON Ou std::numeric_limits<float>::epsilon . Voir aussi machine epsilon sur Wikipedia , qui explique comment epsilon est lié aux erreurs d'arrondi.

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

La version plus générale de ceci (pour les nombres autres que 1.0) est appelée 1 unité en dernier lieu (de la mantisse). Voir Wikipédia article ULP .

41
6502

min est la plus petite valeur non nulle qu'un flottant (de forme normalisée) peut prendre, c'est-à-dire quelque chose autour de 2-126 (-126 est l'exposant minimum autorisé pour un flottant); maintenant, si vous le additionnez à 1, vous obtiendrez toujours 1, car un float n'a que 23 bits de mantisse, donc un si petit changement ne peut pas être représenté dans un si grand "nombre" (vous auriez besoin d'un Mantisse de 126 bits pour voir un changement sommant 2-126 à 1).

La modification minimale possible à 1, à la place, est epsilon (la soi-disant machine epsilon), qui est en fait 2-23 - car elle affecte le dernier bit de la mantisse.

20
Matteo Italia

Pour augmenter/décrémenter une valeur à virgule flottante du plus petit nombre possible, utilisez nextafter vers +/- infinity().

Si vous utilisez simplement next_after(x,std::numeric_limits::max()), le résultat est incorrect 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

}
4
Johan Lundberg