Avertissement C ++: division du double par zéro

98

Cas 1:

#include <iostream>

int main()
{
    double d = 15.50;
    std::cout<<(d/0.0)<<std::endl;
}

Il compile sans aucun avertissement et imprime inf. OK, C ++ peut gérer la division par zéro, ( voir en direct ).

Mais,

Cas 2:

#include <iostream>

int main()
{
    double d = 15.50;
    std::cout<<(d/0)<<std::endl;
}

Le compilateur donne l'avertissement suivant ( voyez-le en direct ):

warning: division by zero [-Wdiv-by-zero]
     std::cout<<(d/0)<<std::endl;

Pourquoi le compilateur donne-t-il un avertissement dans le second cas?

Est-ce 0 != 0.0?

Éditer:

#include <iostream>

int main()
{
    if(0 == 0.0)
        std::cout<<"Same"<<std::endl;
    else
        std::cout<<"Not same"<<std::endl;
}

production:

Same
Jayesh
la source
9
Je suppose qu'il prend zéro comme entier dans le deuxième cas et supprime un avertissement, même si le calcul serait fait en utilisant le double plus tard (ce qui, je pense, devrait être le comportement lorsque d est un double).
Qubit le
10
C'est vraiment un problème de QoI. Ni l'avertissement ni l'absence d'avertissement ne sont exigés par la norme C ++ elle-même. Utilisez-vous GCC?
StoryTeller - Unslander Monica
5
@StoryTeller Qu'est-ce que QoI? en.wikipedia.org/wiki/QoI ?
user202729
5
En ce qui concerne votre dernière question, "0 est-il égal à 0,0?" La réponse est que les valeurs sont les mêmes, mais comme vous l'avez découvert, cela ne signifie pas qu'elles sont identiques. Différents types! Tout comme «A» n'est pas identique à 65.
Mr Lister

Réponses:

108

La division en virgule flottante par zéro est bien définie par IEEE et donne l'infini (positive ou négative selon la valeur du numérateur (ou NaNpour ± 0) ).

Pour les entiers, il n'y a aucun moyen de représenter l'infini et le langage définit l'opération comme ayant un comportement indéfini afin que le compilateur essaie utilement de vous éloigner de ce chemin.

Cependant dans ce cas, puisque le numérateur est a double, le diviseur ( 0) devrait également être promu au double et il n'y a aucune raison de donner un avertissement ici sans donner d'avertissement pour 0.0donc je pense que c'est un bogue du compilateur.

Motti
la source
8
Les deux sont cependant des divisions en virgule flottante. Dans d/0, 0est converti en type de d.
43
Notez que C ++ n'est pas nécessaire pour utiliser IEEE 754 (même si je n'ai jamais vu de compilateur utilisant un standard différent).
Yksisarvinen
1
@hvd, bon point, dans ce cas, cela ressemble à un bogue du compilateur
Motti
14
Je suis d'accord pour dire qu'il devrait soit avertir dans les deux cas, soit ne pas avertir dans les deux cas (en fonction de la gestion de la division flottante par zéro par le compilateur)
MM
8
À peu près sûr que la division en virgule flottante par zéro est également UB - c'est juste que GCC l'implémente selon IEEE 754. Ils n'ont pas à faire cela cependant.
Martin Bonner soutient Monica le
42

En C ++ standard, les deux cas sont des comportements indéfinis . Tout peut arriver, y compris le formatage de votre disque dur. Vous ne devez pas vous attendre ou compter sur "return inf. Ok" ou tout autre comportement.

Le compilateur décide apparemment de donner un avertissement dans un cas et pas dans l'autre, mais cela ne signifie pas qu'un code est OK et l'autre ne l'est pas. C'est juste une bizarrerie de la génération d'avertissements du compilateur.

À partir de la norme C ++ 17 [expr.mul] / 4:

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 égal à zéro, le comportement n'est pas défini.

MM
la source
21
Ce n'est pas vrai, en arithmétique en virgule flottante, la division par zéro est bien définie.
Motti
9
@Motti - Si l'on se limite au seul standard C ++, il n'y a pas de telles garanties. La portée de cette question n'est pas bien précisée, pour être franc.
StoryTeller - Unslander Monica
9
@StoryTeller Je suis assez sûr (bien que je ne l' ai pas regardé le document lui - même norme pour cela) que si std::numeric_limits<T>::is_iec559est true, puis division par zéro pour Tne UB (et sur la plupart des plates - formes c'est truepour doubleet float, bien que pour être portable , vous aurez besoin de vérifier explicitement cela avec un ifou if constexpr).
Daniel H
6
@DanielH - "Raisonnable" est en fait assez subjectif. Si cette question était étiquetée langue-avocat, il y aurait un tout autre ensemble (beaucoup plus petit) d'hypothèses raisonnables.
StoryTeller - Unslander Monica
5
@MM N'oubliez pas que c'est exactement ce que vous avez dit, mais rien de plus: indéfini ne signifie pas qu'aucune exigence n'est imposée, cela signifie qu'aucune exigence n'est imposée par la norme . Je dirais que dans ce cas, le fait qu'une implémentation soit définie is_iec559comme truesignifie que l' implémentation documente un comportement que la norme laisse indéfini. C'est juste que c'est un cas où la documentation de l'implémentation peut être lue par programme. Pas même le seul: il en va de même is_modulopour les types entiers signés.
12

Ma meilleure supposition pour répondre à cette question particulière serait que le compilateur émette un avertissement avant d' effectuer la conversion de inten double.

Ainsi, les étapes seraient comme ceci:

  1. Expression d'analyse
  2. Opérateur arithmétique /(T, T2) , où T=double, T2=int.
  3. Vérifiez que std::is_integral<T2>::valuec'est trueet b == 0- cela déclenche un avertissement.
  4. Émettre un avertissement
  5. Effectuer une conversion implicite de T2endouble
  6. Effectuer une division bien définie (puisque le compilateur a décidé d'utiliser IEEE 754).

Ceci est bien sûr une spéculation et est basé sur des spécifications définies par le compilateur. Du point de vue standard, nous traitons d'éventuels comportements indéfinis.


Veuillez noter que c'est un comportement attendu selon la documentation de GCC
(btw. Il semble que cet indicateur ne puisse pas être utilisé explicitement dans GCC 8.1)

-Wdiv-by-zero
Avertir de la division par zéro au moment de la compilation. C'est par défaut. Pour empêcher les messages d'avertissement, utilisez -Wno-div-by-zero. La division en virgule flottante par zéro n'est pas mise en garde, car elle peut être un moyen légitime d'obtenir des infinis et des NaN.

Yksisarvinen
la source
2
Ce n'est pas ainsi que fonctionnent les compilateurs C ++. Le compilateur doit effectuer une résolution de surcharge /pour savoir qu'il s'agit d'une division. Si le côté gauche avait été un Fooobjet et qu'il y aurait eu un operator/(Foo, int), alors ce ne serait même pas une division. Le compilateur ne connaît sa division que lorsqu'il a choisi en built-in / (double, double)utilisant une conversion implicite du côté droit. Mais cela signifie qu'il ne fait PAS une division par int(0), mais une division par double(0).
MSalters
@MSalters Veuillez voir ceci. Ma connaissance du C ++ est limitée, mais selon la référence operator /(double, int)est certainement acceptable. Ensuite, il dit que la conversion est effectuée avant toute autre action, mais GCC pourrait vérifier rapidement si T2est de type entier et b == 0et émettre un avertissement si c'est le cas. Je ne sais pas si c'est entièrement conforme à la norme, mais les compilateurs ont toute liberté pour définir les avertissements et quand ils doivent être déclenchés.
Yksisarvinen
2
Nous parlons ici de l'opérateur intégré. C'est drôle. Ce n'est pas réellement une fonction, donc vous ne pouvez pas prendre son adresse. Par conséquent, vous ne pouvez pas déterminer s'il operator/(double,int)existe réellement. Le compilateur peut par exemple décider d'optimiser la a/bconstante ben le remplaçant par a * (1/b). Bien sûr, cela signifie que vous n'appelez plus operator/(double,double)au moment de l'exécution, mais le plus rapidement operator*(double,double). Mais c'est maintenant l'optimiseur qui 1/0operator*
trébuche
@MSalters Généralement, la division en virgule flottante ne peut pas être remplacée par une multiplication, probablement sauf dans des cas exceptionnels, tels que 2.
user202729
2
@ user202729: GCC le fait même pour la division entière . Laissez cela pénétrer un instant. GCC remplace la division entière par la multiplication entière. Oui, c'est possible, car GCC sait qu'il fonctionne sur un anneau (numéros modulo 2 ^ N)
MSalters
9

Je n'entrerai pas dans la débâcle UB / non UB dans cette réponse.

Je veux juste souligner cela 0et 0.0 sont différents malgré l' 0 == 0.0évaluation vraie. 0est un intlittéral et 0.0est un doublelittéral.

Cependant, dans ce cas, le résultat final est le même: d/0est une division en virgule flottante car elle dest double et 0est donc implicitement convertie en double.

bolov
la source
5
Je ne vois pas en quoi cela est pertinent, étant donné que les conversions arithmétiques habituelles spécifient que diviser a doublepar un intsignifie que le intest converti en double , et il est spécifié dans la norme qui se 0convertit en 0.0 (conv.fpint / 2)
MM
@MM l'OP veut savoir si 0est le même que0.0
bolov
2
La question dit "Est-ce 0 != 0.0?". OP ne demande jamais s'ils sont «les mêmes». De plus, il me semble que l'intention de la question est de savoir si d/0peut se comporter différemment ded/0.0
MM
2
@MM - Le PO a demandé . Ils ne montrent pas vraiment une netiquette SO décente avec ces modifications de constantes.
StoryTeller - Unslander Monica
7

Je dirais que foo/0et nefoo/0.0 sont pas les mêmes. À savoir, l'effet résultant du premier (division entière ou division en virgule flottante) dépend fortement du type de foo, alors qu'il n'en est pas de même pour le second (ce sera toujours une division en virgule flottante).

Que l'un des deux soit UB n'est pas pertinent. Citant la norme:

Le comportement indéfini autorisé va de l'ignorance totale de la situation avec des résultats imprévisibles, au comportement pendant la traduction ou l'exécution du programme d'une manière documentée caractéristique de l'environnement (avec ou sans l'émission d'un message de diagnostic) , à la fin d'une traduction ou d'une exécution (avec l'émission d'un message de diagnostic).

(Je souligne le mien)

Considérez l' avertissement « suggérer des parenthèses autour de l'affectation utilisée comme valeur de vérité »: La façon de dire au compilateur que vous voulez vraiment utiliser le résultat d'une affectation est d'être explicite et d'ajouter des parenthèses autour de l'affectation. L'instruction résultante a le même effet, mais elle indique au compilateur que vous savez ce que vous faites. La même chose peut être dite à propos de foo/0.0: Puisque vous dites explicitement au compilateur "Ceci est une division en virgule flottante" en utilisant à la 0.0place de 0, le compilateur vous fait confiance et n'émettra pas d'avertissement.

Cássio Renan
la source
1
Les deux doivent subir les conversions arithmétiques habituelles pour les amener à un type commun, ce qui laissera les deux cas une division en virgule flottante.
Shafik Yaghmour
@ShafikYaghmour Vous avez manqué le point de la réponse. Notez que je n'ai jamais mentionné quel est le type de foo. C'est intentionnel. Votre assertion n'est vraie que dans le cas où il foos'agit d'un type à virgule flottante.
Cássio Renan
Je ne l'ai pas fait, le compilateur a des informations de type et comprend les conversions, peut-être qu'un analyseur statique basé sur du texte peut être attrapé par de telles choses, mais le compilateur ne devrait pas.
Shafik Yaghmour
Mon point est que oui, le compilateur connaît les conversions arithmétiques habituelles, mais il choisit de ne pas émettre d'avertissement lorsque le programmeur est explicite. Le fait est que ce n'est probablement pas un bogue, mais plutôt un comportement intentionnel.
Cássio Renan
Ensuite , la documentation que je montrai est incorrecte, car les deux cas sont division à virgule flottante. Donc, soit la documentation est fausse, soit le diagnostic a un bogue.
Shafik Yaghmour
4

Cela ressemble à un bogue gcc, la documentation de -Wno-div-by-zero dit clairement :

N'avertissez pas de la division par zéro au moment de la compilation. La division en virgule flottante par zéro n'est pas mise en garde, car elle peut être un moyen légitime d'obtenir des infinis et des NaN.

et après les conversions arithmétiques habituelles couvertes dans [expr.arith.conv] les deux opérandes seront doubles :

De nombreux opérateurs binaires qui attendent des opérandes de type arithmétique ou énumération provoquent des conversions et produisent des types de résultats de la même manière. Le but est de produire un type commun, qui est également le type du résultat. Ce modèle est appelé les conversions arithmétiques habituelles, qui sont définies comme suit:

...

- Sinon, si l'un des opérandes est double, l'autre doit être converti en double.

et [expr.mul] :

Les opérandes de * et / doivent avoir un type d'énumération arithmétique ou sans portée; les opérandes de% doivent être de type énumération intégrale ou non cadrée. Les conversions arithmétiques usuelles sont effectuées sur les opérandes et déterminent le type du résultat.

En ce qui concerne la question de savoir si la division en virgule flottante par zéro est un comportement indéfini et la manière dont une implémentation différente la traite, je pense que ma réponse ici . TL, DR; Il semble que gcc soit conforme à l' Annexe F en ce qui concerne la division en virgule flottante par zéro, donc undefined ne joue pas un rôle ici. La réponse serait différente pour clang.

Shafik Yaghmour
la source
2

La division en virgule flottante par zéro se comporte différemment de la division entière par zéro.

La norme de virgule flottante IEEE fait la différence entre + inf et -inf, tandis que les entiers ne peuvent pas stocker l'infini. La division entière par zéro est un comportement indéfini. La division en virgule flottante par zéro est définie par la norme à virgule flottante et donne + inf ou -inf.

Rizwan
la source
2
C'est vrai, mais on ne sait pas en quoi cela est pertinent pour la question, car la division en virgule flottante est effectuée dans les deux cas . Il n'y a pas de division entière dans le code d'OP.
Konrad Rudolph