Tout le monde sait que vous n'êtes pas censé comparer directement les flottants, mais plutôt en utilisant une tolérance:
float a,b;
float epsilon = 1e-6f;
bool equal = (fabs(a-b) < epsilon);
Je me demandais si la même chose s'applique à la comparaison d'une valeur à zéro avant de l'utiliser en division.
float a, b;
if (a != 0.0f) b = 1/a; // oops?
Dois-je également comparer avec epsilon dans ce cas?
La division en virgule flottante par zéro n'est pas une erreur. Il déclenche une exception à virgule flottante (qui est un no-op, sauf si vous les vérifiez activement) sur les implémentations qui prennent en charge les exceptions à virgule flottante, et a un résultat bien défini: infini positif ou négatif (si le numérateur est différent de zéro), ou NAN (si le numérateur est zéro).
Il est également possible d'obtenir l'infini (et une exception de débordement) comme résultat lorsque le dénominateur est différent de zéro mais très proche de zéro (par exemple, sous-normal), mais là encore, ce n'est pas une erreur. C'est juste comment fonctionne la virgule flottante.
Edit: Notez que, comme Eric l'a souligné dans les commentaires, cette réponse suppose les exigences de l'annexe F, une partie facultative de la norme C détaillant le comportement en virgule flottante et l'alignant sur la norme IEEE pour le flottant point. En l'absence d'arithmétique IEEE, C ne définit pas la division en virgule flottante par zéro (et en fait, les résultats de toutes les opérations en virgule flottante sont définis par l'implémentation et peuvent être définis comme un non-sens complet et toujours conformes à la norme C), donc si vous avez affaire à une implémentation C bizarre qui n'honore pas la virgule flottante IEEE, vous devrez consulter la documentation de l'implémentation que vous utilisez pour répondre à cette question.
Oui, la division par de petits nombres peut avoir les mêmes effets que la division par zéro, y compris les pièges, dans certaines situations.
Certaines implémentations C (et certains autres environnements informatiques) peuvent s'exécuter dans un mode de vidage par sous-flux, en particulier si des options de hautes performances sont utilisées. Dans ce mode, la division par un dénormal peut entraîner le même résultat que la division par zéro. Le mode de vidage par le dessous n'est pas rare lorsque des instructions vectorielles (SIMD) sont utilisées.
Les nombres dénormaux sont ceux avec l'exposant minimum dans le format à virgule flottante qui sont si petits que le bit implicite de la signification est 0 au lieu de 1. Pour IEEE 754, simple précision, il s'agit de nombres non nuls avec une magnitude inférieure à 2-126. Pour la double précision, il s'agit de nombres non nuls de magnitude inférieure à 2-1022.
La gestion correcte des nombres dénormaux (conformément à IEEE 754) nécessite un temps de calcul supplémentaire dans certains processeurs. Pour éviter ce délai lorsqu'il n'est pas nécessaire, les processeurs peuvent avoir un mode qui convertit les opérandes dénormaux à zéro. La division d'un nombre par un opérande dénormal produira alors le même résultat que la division par zéro, même si le résultat habituel serait fini.
Comme indiqué dans d'autres réponses, la division par zéro n'est pas une erreur dans les implémentations C qui adoptent l'annexe F de la norme C. Pas toutes les implémentations qui le font. Dans les implémentations qui ne le font pas, vous ne pouvez pas être sûr que les interruptions en virgule flottante sont activées, en particulier l'interruption pour l'exception de division par zéro, sans spécifications supplémentaires sur votre environnement.
Selon votre situation, vous devrez peut-être également vous prémunir contre tout autre code de votre application modifiant l'environnement à virgule flottante.
Pour répondre à la question dans le titre de votre message, la division par un très petit nombre ne provoquera pas une division par zéro, mais cela peut faire en sorte que le résultat devienne un infini:
double x = 1E-300;
cout << x << endl;
double y = 1E300;
cout << y << endl;
double z = y / x;
cout << z << endl;
cout << (z == std::numeric_limits<double>::infinity()) << endl;
Ceci produit la sortie suivante:
1e-300
1e+300
inf
1
Seule une division par exactement 0.f lèvera une division par zéro exception.
Cependant, la division par un très petit nombre peut générer une exception de dépassement de capacité - le résultat est si grand qu'il ne peut plus être représenté par un flottant. La division rendra l'infini.
La représentation flottante de l'infini peut être utilisée dans les calculs, il n'est donc pas nécessaire de la vérifier si le reste de votre implémentation peut la gérer.
Dois-je également comparer avec epsilon dans ce cas?
Vous ne recevrez jamais d'erreur de division par zéro, car 0.0f
est représenté exactement dans un flottant IEEE .
Cela étant dit, vous pouvez toujours vouloir utiliser une certaine tolérance - bien que cela dépende complètement de votre application. Si la valeur "zéro" est le résultat d'autres calculs, il est possible d'obtenir un très petit nombre non nul, ce qui peut provoquer un résultat inattendu après votre division. Si vous souhaitez traiter les nombres "proches de zéro" comme des zéros, une tolérance serait appropriée. Cependant, cela dépend complètement de votre application et de vos objectifs.
Si votre compilateur utilise normes IEEE 754 pour la gestion des exceptions , puis divisez par zéro, ainsi qu'une division par une valeur suffisamment petite pour provoquer un débordement, entraînerait une valeur de +/- infiniti. Cela pourrait signifier que vous pourriez vouloir inclure la vérification des très petits nombres (cela provoquerait un débordement sur votre plate-forme). Par exemple, sur Windows , float
et double
sont tous les deux conformes aux spécifications, ce qui pourrait provoquer un très petit diviseur pour créer +/- infiniti, tout comme un zéro valeur.
Si votre compilateur/plate-forme ne respecte pas les normes à virgule flottante IEEE 754, je pense que les résultats sont spécifiques à la plate-forme.