web-dev-qa-db-fra.com

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

Considérer

#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 sera NaN. Mais j'entends également des rumeurs selon lesquelles à strictement parler le comportement de la division en virgule flottante par zéro est non défini et donc le code ci-dessus ne peut pas être considéré comme C++ portable. (Cela efface théoriquement l'intégrité de ma pile de millions de lignes et de code. Oups.)

Qui a raison?

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

47
Bathsheba

La division par zéro à la fois entier et virgule flottante est 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 deuxième opérande de/ou% est nul, le comportement n'est pas défini. ...

Bien que la mise en œuvre 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 dans ce rapport de bogue de clang le désinfectant de clang considère IEC 60559 division à virgule flottante par zéro comme non défini que même si la macro __ STDC_IEC_559 __ est défini, il est défini par les en-têtes du système et au moins pour clang ne prend pas en charge 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 la division en virgule flottante par zéro, mais clang (3.3 et 3.4 instantané Debian) la considère comme non définie. Ceci est une erreur:

La prise en charge de l'annexe F est facultative et nous ne la prenons pas en charge.

#if STDC_IEC_559

Cette macro est définie par vos en-têtes système, pas par nous; c'est un bogue 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 bogue spécifique à Clang.)

Ce rapport de bogue et deux autres rapports de bogue BSan: 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 (CEI 60559/IEEE 754) indique 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 sans condition, 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 modifié. De nos jours, ne prend pas en charge IEEE 754 (au moins les fonctionnalités de base comme la gestion de la division par zéro) est considérée comme un mauvais comportement.

Ceci est également pris en charge par le gcc Sémantique des mathématiques en virgule flottante dans le wiki de GCC qui indique que - fno-signaling-nans est la valeur par défaut qui est d'accord avec le gcc documentation des options d'optimisation qui dit:

La valeur par défaut est -fno-signaling-nans.

Il est intéressant de noter que BSan pour clang par défaut à inclure float-divide-by-zero under - fsanitize = undefined tandis que pas gcc :

Détecte 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, car la division en virgule flottante par zéro peut être un moyen légitime d'obtenir des infinis et des NaN .

Voir en direct pour clang et en direct pour gcc .

6
Shafik Yaghmour

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 ... cela dépend.

Indéfini signifie, l'implémentation du compilateur décide, et il existe de nombreuses variables comme l'architecture matérielle, l'efficacité de la génération de code, la paresse des développeurs 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 nul, le comportement n'est pas défini

Norme C++ 18.3.2.4

... statique constexpr bool is_iec559;

... 56. Vrai si et seulement si le type respecte la norme IEC 559.217

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

Détection C++ de IEEE754:

La bibliothèque standard comprend 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 fait terminer l'application.

39
Adrian Maire

Citant cppreference :

Si le deuxième opérande est nul, le comportement n'est pas défini, sauf que si une 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 ), puis:

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

  • la division d'un nombre non nul par ± 0,0 donne l'infini correctement signé et FE_DIVBYZERO est relevé

  • diviser 0,0 par 0,0 donne NaN et FE_INVALID est relevé

Nous parlons ici de division en virgule flottante, donc il est en fait défini par l'implémentation si la division double par zéro n'est pas définie.

Si std::numeric_limits<double>::is_iec559 est true, et c'est "habituellement true" , alors le comportement est bien défini et produit les résultats attendus.

Un pari assez sûr serait de faire tomber:

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

... près de votre code.

23
Quentin

La division par 0 est un comportement indéfini .

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

Le binaire / L'opérateur donne le quotient et le binaire % L'opérateur renvoie le reste de la division de la première expression par la seconde. Si le deuxième opérande de / ou % est nul le comportement n'est pas défini. Pour les opérandes intégraux, le / L'opérateur donne le quotient algébrique avec toute partie fractionnaire rejetée; 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 le / opérateur. La norme indique seulement que la division par zéro n'est pas définie sans égard aux opérandes.

8
dbush

Dans [expr]/4, nous avons

Si lors de l'évaluation d'une expression, le résultat n'est pas défini mathématiquement ou pas dans la plage de valeurs représentables pour son type, le comportement n'est pas défini. [Remarque: la plupart des implémentations existantes de C++ ignorent les débordements d'entiers. Le traitement de la division par zéro, formant un reste à l'aide d'un diviseur nul, et toutes les exceptions à virgule flottante varient selon les machines et sont généralement ajustables par une fonction de bibliothèque. —Fin note]

Emphase mine

Donc, selon la norme, c'est un comportement non défini. Il continue à dire que certains de ces cas sont réellement traités par l'implémentation et sont configurables. Donc, il ne dira pas qu'il est défini par l'implémentation, mais il vous indique que les implémentations définissent certains de ces comportements.

6
NathanOliver

En ce qui concerne la question du demandeur "Qui a raison?", Il est parfaitement correct de dire que les réponses both sont correctes. Le fait que la norme C décrit le comportement comme "non dé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 utilisez un matériel qui implémente la norme IEEE, vous constaterez que l'opération est en fait implémentée, avec les résultats stipulés par la norme IEEE.

1
PMar

Cela dépend également de l'environnement en 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 variantes spécifiques à la plate-forme qui sont antérieures à la standardisation de tout cela.

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

0
Paul Floyd