Une valeur flottante proche de zéro peut-elle provoquer une erreur de division par zéro?

Tout le monde sait que vous n’êtes pas censé comparer directement les flotteurs, mais plutôt utiliser une tolérance:

float a,b; float epsilon = 1e-6f; bool equal = (fabs(ab) < 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 génère une exception à virgule flottante (qui est une opération à moins que vous ne les vérifiiez activement) sur les implémentations prenant en charge les exceptions à virgule flottante et un résultat bien défini: infini positif ou négatif (si le numérateur est différent de zéro) NAN (si le numérateur est zéro).

Il est également possible d’obtenir l’infini (et une exception de dépassement) comme résultat lorsque le dénominateur est différent de zéro mais très proche de zéro (par exemple, subnormal), mais là encore, ce n’est pas une erreur. C’est juste comment fonctionne le point flottant.

Edit: Notez que, comme Eric l’a souligné dans les commentaires, cette réponse suppose les exigences de l’Annexe F, une partie facultative du standard C détaillant le comportement des virgules flottantes et son alignement avec la norme IEEE pour les virgules flottantes. 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 non-sens et conformes au standard C). vous avez affaire à une implémentation C hors du commun qui ne respecte 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 en petits nombres peut entraîner 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 débordement, en particulier si des options de haute performance 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 Flush-underflow n’est pas rare lorsque des instructions vectorielles (SIMD) sont utilisées.

Les nombres dénormalisés sont ceux avec l’exposant minimum au format à virgule flottante qui sont si petits que le bit implicite du significande est 0 au lieu de 1. Pour IEEE 754, simple précision, ce sont des nombres non nuls dont l’amplitude est inférieure à 2 -126 . Pour la double précision, ce sont des nombres non nuls dont la magnitude est inférieure à 2 -1022 .

La gestion correcte des nombres dénormalisés (conformément à IEEE 754) nécessite un temps de calcul supplémentaire dans certains processeurs. Pour éviter ce délai lorsque cela n’est pas nécessaire, les processeurs peuvent avoir un mode qui convertit les opérandes dénormalisés à zéro. Diviser 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 à virgule flottante sont activées, en particulier l’interruption de l’exception de division par zéro, sans spécifications supplémentaires concernant votre environnement.

Selon votre situation, vous devrez peut-être également vous prémunir contre d’autres codes dans votre application modifiant l’environnement en virgule flottante.

Pour répondre à la question dans le titre de votre message, diviser par un très petit nombre ne provoquera pas de division par zéro, mais le résultat pourrait devenir 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::infinity()) << endl; 

Cela produit la sortie suivante:

 1e-300 1e+300 inf 1 

Seule une division de exactement 0.f générera une exception de division par zéro.

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 important qu’il ne peut plus être représenté par un flottant. La division retournera l’infini.

La représentation flottante de l’infini peut être utilisée dans les calculs, il n’est donc peut-être pas nécessaire de la vérifier si le rest de votre implémentation peut le gérer.

Dois-je également comparer avec epsilon dans ce cas?

Vous ne recevrez jamais une 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, même si cela dépend entièrement 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 entraîner un résultat inattendu après votre division. Si vous voulez traiter les nombres “proches de zéro” comme étant zéro, une tolérance serait appropriée. Cela dépend complètement de votre application et de vos objectives.

Si votre compilateur utilise les normes IEEE 754 pour la gestion des exceptions , la division par zéro, ainsi qu’une division par une valeur suffisamment petite pour provoquer un débordement, entraîneraient une valeur de +/- infiniti. Cela pourrait signifier que vous pourriez vouloir inclure le chèque pour de très petits nombres (cela provoquerait un débordement sur votre plate-forme). Par exemple, sous Windows , float et double sont tous deux conformes aux spécifications, ce qui pourrait créer un diviseur très petit +/- infiniti, tout comme une valeur nulle.

Si votre compilateur / plate-forme ne suit pas les normes IEEE 754 en virgule flottante, je pense que les résultats sont spécifiques à la plate-forme.