Le comportement de la division en virgule flottante par zéro

Considérez

#include <iostream>
int main()
{
    double a = 1.0 / 0;
    double b = -1.0 / 0;
    double c = 0.0 / 0;
    std::cout << a << b << c; // to stop compilers from optimising out the code.    
}

J'ai toujours pensé que a sera +Inf b sera -Inf, et c NaN. Mais j'entends aussi des rumeurs selon lesquelles à proprement parler le comportement de la division en virgule flottante par Zéro est indéfini et donc le code ci-dessus ne peut pas être considéré comme portable c++. (Cela efface théoriquement l'intégrité de ma pile de code Million line plus. Oops.)

Qui a raison?

Notez que je suis satisfait de implémentation définie , mais Je parle de manger des chats, éternuer des démons comportement indéfini ici.

41
demandé sur Shafik Yaghmour 2017-03-21 15:10:03

7 réponses

Division par zéro les deux entiers et virgule flottante sont un comportement indéfini [expr.mul]p4:

L'opérateur binaire / donne le quotient, et l'opérateur binaire % donne le reste de la division de la première expression par la seconde. Si le second opérande de / ou % est égal à zéro, le comportement n'est pas défini. ...

Bien que l'implémentation puisse éventuellement prendre en charge Annexe F qui a une sémantique bien définie pour la division en virgule flottante par zéro.

Nous pouvons voir à partir de ce rapport de bogue clang clang sanitizer considère IEC 60559 division à virgule flottante par zéro comme indéfini {[2] } que même si la macro _ _ STDC _ IEC _ 559_ _ est définie, elle est définie par les en-têtes du système et au moins pour clang ne supporte pas Annexe F et donc pour clang reste un comportement indéfini:

L'Annexe F de la norme C (Prise en charge IEC 60559 / IEEE 754) définit division en virgule flottante par zéro, mais clang (3.3 et 3.4 instantané Debian) le considère comme indéfini. Ceci est incorrect:

La prise en charge de L'Annexe F est facultative, et nous ne la soutenons pas.

# if STDC_IEC_559

Cette macro est définie par les en-têtes de votre système, pas par nous; c'est un bug dans les en-têtes de votre système. (FWIW, GCC ne prend pas entièrement en charge L'annexe F non plus, IIRC, donc ce n'est même pas un bug spécifique à Clang.)

Ce rapport de bogue et deux autres bogues rapports UBSan: la division en virgule flottante par zéro n'est pas indéfinie et clang devrait prendre en charge L'Annexe F de L'ISO C (IEC 60559 / IEEE 754) indiquent que gcc est conforme à Annexe F en ce qui concerne la division en virgule flottante par zéro.

Bien que je convienne qu'il n'appartient pas à la bibliothèque C de définir STDC_IEC_559 inconditionnellement, le problème est spécifique à clang. GCC ne prend pas entièrement en charge L'Annexe F, mais au moins son intention est de la prendre en charge par défaut et la division est bien définie avec elle si le mode d'arrondi n'est pas changé. de nos jours, ne pas supporter IEEE 754 (au moins les fonctionnalités de base comme la gestion de la division par Zéro) est considéré comme un mauvais comportement.

C'est un support supplémentaire par la sémantique gcc des mathématiques en virgule flottante dans le wiki GCC qui indique que - FNO-signalisation-nans est la valeur par défaut qui est en accord avec la documentation options d'optimisations gcc qui dit:

Le la valeur par défaut est-FNO-signalisation-nans.

Intéressant de noter que UBSan pour clang par défaut, y compris les float-division par zéro, en vertu de -fsanitize=undefined tout gcc ne:

Détecter la division en virgule flottante par zéro. Contrairement à d'autres options similaires, - fsanitize=float-divide-by-zero n'est pas activé par-fsanitize=undefined, puisque la division en virgule flottante par zéro peut être un moyen légitime d'obtenir des infinis et Nan.

Voir live pour clang et live pour gcc.

2
répondu Shafik Yaghmour 2018-07-23 21:54:57

La norme C++ ne force pas la norme IEEE 754, car cela dépend principalement de l'architecture matérielle.

Si le matériel / compilateur implémente correctement la norme IEEE 754, la division fournira les INF, -INF et NaN attendus, sinon... il dépend.

Undefined signifie que l'implémentation du compilateur décide, et il y a beaucoup de variables à cela comme l'architecture matérielle, l'efficacité de la génération de code, la paresse du développeur du compilateur, etc..

Source:

La norme C++ indique qu'une division par 0.0 est undefined

Norme C++ 5.6.4

... Si le deuxième opérande de / ou % est zéro, le comportement est indéfini

Norme C++ 18.3.2.4

...statique constexpr bool is_iec559;

...56. True si et seulement si le type est conforme à la norme CEI 559.217

...57. Significatif pour tous les types à virgule flottante.

Détection c++ de IEEE754:

La bibliothèque standard inclut un modèle pour détecter si IEEE754 est pris en charge ou non:

Statique constexpr bool is_iec559;

#include <numeric>
bool isFloatIeee754 = std::numeric_limits<float>::is_iec559();

Que faire si IEEE754 n'est pas pris en charge?

Cela dépend, généralement une division par 0 déclenche une exception matérielle et met fin à l'application.

37
répondu Adrian Maire 2018-07-24 12:00:23

, Citant cppreference:

Si le deuxième opérande est zéro, le comportement est indéfini, sauf que si la division en virgule flottante a lieu et que le type prend en charge l'arithmétique en virgule flottante IEEE (voir std::numeric_limits::is_iec559), alors:

  • Si un opérande est NaN, le résultat est NaN

  • Diviser un nombre non nul par ±0,0 donne l'infini correctement signé et {[2] } est déclenché

  • Diviser 0,0 par 0,0 donne NaN et {[3] } est soulevé

Nous parlons ici de division à virgule flottante, donc il est en fait défini par l'implémentation si double division par Zéro est indéfini.

Si std::numeric_limits<double>::is_iec559 est true, et il est "habituellement true", ensuite, le comportement est bien définie et produit les résultats attendus.

Un pari assez sûr serait de plop a:

static_assert(std::numeric_limits<double>::is_iec559, "Please use IEEE754, you weirdo");

... près de votre code.

23
répondu Quentin 2017-03-21 12:28:01

La Division par 0 est comportement indéfini.

De la section 5.6 de la norme C++ (C++11):

L'opérateur binaire / donne le quotient, et l'opérateur binaire % donne le reste de la division de la première expression par deuxième. si le deuxième opérande de / ou % est nul, le comportement est indéterminé. pour les opérandes intégraux, l'opérateur / donne la valeur algébrique quotient avec toute partie fractionnaire mis au rebut; si le quotient a/b est représentable dans le type du résultat, (a/b)*b + a%b est égal à a .

Aucune distinction n'est faite entre les opérandes entiers et à virgule flottante pour l'opérateur /. La norme indique seulement que la division par zéro n'est pas définie sans tenir compte des opérandes.

7
répondu dbush 2017-03-22 02:52:43

Dans [expr]/4, nous avons

Si, lors de l'évaluation d'une expression, , le résultat n'est pas définie mathématiquement, ou pas dans la gamme des représentable valeurs pour son type, le comportement est indéfini. [Note: la plupart des implémentations existantes de C++ ignorent les débordements d'entiers. Traitement de la division par zéro, formant un reste en utilisant un diviseur zéro, et toutes les exceptions à virgule flottante varient entre les machines, et est généralement réglable par une fonction de bibliothèque. -la note de fin de ]

L'accent est mis sur le mien

Donc, selon la norme, c'est un comportement indéfini. Il continue à dire que certains de ces cas sont réellement gérés par l'implémentation et sont configurables. Donc, il ne dira pas que c'est l'implémentation définie mais il vous permet de savoir que les implémentations définissent une partie de ce comportement.

6
répondu NathanOliver 2017-03-21 12:25:29

Quant à la question de l'auteur " qui a raison?', il est parfaitement correct de dire que les deux réponses sont correctes. Le fait que la norme C décrit le comportement comme 'indéfini' ne dicte pas ce que fait réellement le matériel sous-jacent; cela signifie simplement que si vous voulez que votre programme soit significatif selon la norme vous - ne pouvez pas supposer-que le matériel implémente réellement cette opération. Mais si vous êtes en cours d'exécution sur du matériel qui implémente L'IEEE standard, vous trouverez que l'opération est en fait implémentée, avec les résultats stipulés par la norme IEEE.

1
répondu PMar 2017-03-21 13:57:10

Cela dépend également de l'environnement à virgule flottante.

Cppreference a des détails: http://en.cppreference.com/w/cpp/numeric/fenv (pas d'exemples cependant).

Cela devrait être disponible dans la plupart des environnements de bureau/serveur C++11 et C99. Il existe également des variations spécifiques à la plate-forme qui sont antérieures à la standardisation de tout cela.

Je m'attendrais à ce que l'activation des exceptions fasse fonctionner le code plus lentement, donc probablement pour cette raison la plupart des plates-formes que je connais désactiver les exceptions par défaut.

0
répondu Paul Floyd 2017-03-21 13:22:01