C ++ Warnung: Division von Double durch Null

98

Fall 1:

#include <iostream>

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

Es wird ohne Warnungen kompiliert und gedruckt inf. OK, C ++ kann die Division durch Null verarbeiten ( siehe live ).

Aber,

Fall 2:

#include <iostream>

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

Der Compiler gibt die folgende Warnung aus ( sehen Sie es live ):

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

Warum warnt der Compiler im zweiten Fall?

Ist 0 != 0.0?

Bearbeiten:

#include <iostream>

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

Ausgabe:

Same
Jayesh
quelle
9
Ich gehe davon aus, dass es im zweiten Fall Null als Ganzzahl nimmt und eine Warnung löscht, selbst wenn die Berechnung später mit double erfolgen würde (was meiner Meinung nach das Verhalten sein sollte, wenn d ein Double ist).
Qubit
10
Es ist wirklich ein QoI-Problem. Weder die Warnung noch das Fehlen einer Warnung wird vom C ++ - Standard selbst vorgeschrieben. Verwenden Sie GCC?
Geschichtenerzähler - Unslander Monica
5
@StoryTeller Was ist QoI? en.wikipedia.org/wiki/QoI ?
user202729
5
In Bezug auf Ihre letzte Frage: "Ist 0 gleich 0.0?" Die Antwort ist, dass die Werte gleich sind, aber wie Sie herausgefunden haben, heißt das nicht, dass sie identisch sind. Verschiedene Typen! Genau wie 'A' ist nicht identisch mit 65.
Herr Lister

Antworten:

108

Die Gleitkommadivision durch Null ist durch IEEE gut definiert und ergibt unendlich (entweder positiv oder negativ, je nach Wert des Zählers (oder NaNfür ± 0) ).

Für Ganzzahlen gibt es keine Möglichkeit, Unendlichkeit darzustellen, und die Sprache definiert die Operation als undefiniertes Verhalten, sodass der Compiler hilfreich versucht, Sie von diesem Pfad fernzuhalten.

In diesem Fall sollte doubleder Divisor ( 0) jedoch , da der Zähler a ist , ebenfalls auf ein Double heraufgestuft werden, und es gibt keinen Grund, hier eine Warnung zu geben, ohne dafür eine Warnung zu geben. 0.0Ich denke, dies ist ein Compiler-Fehler.

Motti
quelle
8
Beide sind jedoch Gleitkommadivisionen. In d/0, 0wird auf die Art von umgerechnet d.
43
Beachten Sie, dass C ++ nicht erforderlich ist, um IEEE 754 zu verwenden (obwohl ich noch nie einen Compiler mit einem anderen Standard gesehen habe).
Yksisarvinen
1
@hvd, guter Punkt, es, dass dies wie ein Compiler-Fehler aussieht
Motti
14
Ich würde zustimmen, dass es entweder in beiden Fällen warnen oder in beiden Fällen nicht warnen sollte (abhängig davon, wie der Compiler mit der schwebenden Division durch Null umgeht)
MM
8
Ziemlich sicher, dass die Gleitkommadivision durch Null auch UB ist - es ist nur so, dass GCC sie gemäß IEEE 754 implementiert. Das müssen sie aber nicht.
Martin Bonner unterstützt Monica
42

In Standard C ++ sind beide Fälle undefiniertes Verhalten . Alles kann passieren, einschließlich der Formatierung Ihrer Festplatte. Sie sollten "return inf. Ok" oder ein anderes Verhalten nicht erwarten oder sich darauf verlassen.

Der Compiler beschließt anscheinend, in einem Fall eine Warnung zu geben und nicht in dem anderen, aber dies bedeutet nicht, dass ein Code in Ordnung ist und einer nicht. Es ist nur eine Eigenart der Generierung von Warnungen durch den Compiler.

Aus dem C ++ 17-Standard [expr.mul] / 4:

Der binäre /Operator ergibt den Quotienten, und der binäre %Operator ergibt den Rest aus der Division des ersten Ausdrucks durch den zweiten. Wenn der zweite Operand von /oder %Null ist, ist das Verhalten undefiniert.

MM
quelle
21
Nicht wahr, in der Gleitkomma-Arithmetik ist die Division durch Null gut definiert.
Motti
9
@Motti - Wenn man sich nur auf den C ++ - Standard beschränkt, gibt es keine solchen Garantien. Der Umfang dieser Frage ist nicht genau festgelegt, um ehrlich zu sein.
Geschichtenerzähler - Unslander Monica
9
@StoryTeller Ich bin mir ziemlich sicher (obwohl ich mir das Standarddokument selbst nicht angesehen habe), dass wenn dies der Fall std::numeric_limits<T>::is_iec559ist true, die Division durch Null für Tnicht UB ist (und auf den meisten Plattformen ist es truefür doubleund float, obwohl Sie portabel sind, werden Sie müssen dies explizit mit einem ifoder if constexpr) überprüfen .
Daniel H
6
@ DanielH - "Vernünftig" ist eigentlich ziemlich subjektiv. Wenn diese Frage als Sprachanwalt gekennzeichnet wäre, gäbe es eine ganze Reihe anderer (viel kleinerer) vernünftiger Annahmen.
Geschichtenerzähler - Unslander Monica
5
@MM Denken Sie daran, dass es genau das ist, was Sie gesagt haben, aber nichts weiter: Undefiniert bedeutet nicht, dass keine Anforderungen gestellt werden, es bedeutet, dass der Standard keine Anforderungen stellt . Ich würde argumentieren, dass in diesem Fall die Tatsache, dass eine Implementierung definiert is_iec559als truebedeutet, dass die Implementierung ein Verhalten dokumentiert, das der Standard undefiniert lässt. Dies ist nur ein Fall, in dem die Dokumentation der Implementierung programmgesteuert gelesen werden kann. Nicht einmal der einzige: Gleiches gilt is_modulofür vorzeichenbehaftete Ganzzahltypen.
12

Meine beste Vermutung, um diese spezielle Frage zu beantworten , wäre, dass der Compiler vor der Konvertierung von intin eine Warnung ausgibt double.

Die Schritte wären also wie folgt:

  1. Ausdruck analysieren
  2. Arithmetischer Operator /(T, T2) , wo T=double, T2=int.
  3. Überprüfen Sie, ob dies der Fall std::is_integral<T2>::valueist trueund b == 0- dies löst eine Warnung aus.
  4. Warnung ausgeben
  5. Führen Sie eine implizite Konvertierung von T2nach durchdouble
  6. Führen Sie eine genau definierte Unterteilung durch (da sich der Compiler für IEEE 754 entschieden hat).

Dies ist natürlich Spekulation und basiert auf vom Compiler definierten Spezifikationen. Aus Standardsicht haben wir es mit möglichen undefinierten Verhaltensweisen zu tun.


Bitte beachten Sie, dass dies gemäß der GCC-Dokumentation erwartet wird
(übrigens scheint es, dass dieses Flag in GCC 8.1 nicht explizit verwendet werden kann).

-Wdiv-by-zero Warnt vor der
Ganzzahldivision zur Kompilierungszeit durch Null. Dies ist die Standardeinstellung. Verwenden Sie -Wno-div-by-zero, um die Warnmeldungen zu sperren. Eine Gleitkommadivision durch Null wird nicht gewarnt, da dies ein legitimer Weg sein kann, um Unendlichkeiten und NaNs zu erhalten.

Yksisarvinen
quelle
2
So funktionieren C ++ - Compiler nicht. Der Compiler muss eine Überlastungsauflösung durchführen, um /zu wissen, dass es sich um eine Division handelt. Wenn die linke Seite ein FooObjekt gewesen wäre und es ein Objekt geben würde, wäre operator/(Foo, int)es möglicherweise nicht einmal eine Teilung. Der Compiler kennt seine Aufteilung nur, wenn er built-in / (double, double)eine implizite Konvertierung der rechten Seite verwendet hat. Aber das bedeutet, dass es KEINE Division durch macht int(0), es macht eine Division durch double(0).
MSalters
@ MSalters Bitte sehen Sie dies. Mein Wissen über C ++ ist begrenzt, aber laut Referenz operator /(double, int)ist es sicherlich akzeptabel. Dann heißt es, dass die Konvertierung vor jeder anderen Aktion durchgeführt wird, aber GCC könnte eine schnelle Überprüfung durchführen, ob T2es sich um einen ganzzahligen Typ handelt, b == 0und in diesem Fall eine Warnung ausgeben. Ich bin mir nicht sicher, ob dies vollständig standardkonform ist, aber Compiler haben die volle Freiheit, Warnungen zu definieren und wann sie ausgelöst werden sollten.
Yksisarvinen
2
Wir sprechen hier über den eingebauten Operator. Das ist lustig. Es ist eigentlich keine Funktion, daher können Sie die Adresse nicht übernehmen. Daher können Sie nicht feststellen, ob es operator/(double,int)wirklich existiert. Der Compiler kann beispielsweise entscheiden, die a/bKonstante zu optimieren, bindem er sie durch ersetzt a * (1/b). Das bedeutet natürlich, dass Sie nicht mehr operator/(double,double)zur Laufzeit anrufen, sondern umso schneller operator*(double,double). Aber es ist jetzt der Optimierer 1/0, der operator*
auslöst
@ MSalters Im Allgemeinen kann die Gleitkommadivision nicht durch Multiplikation ersetzt werden, wahrscheinlich abgesehen von Ausnahmefällen wie 2.
user202729
2
@ user202729: GCC macht es sogar für die Ganzzahldivision . Lass das für einen Moment einwirken. GCC ersetzt die Ganzzahldivision durch die Ganzzahlmultiplikation. Ja, das ist möglich, weil GCC weiß, dass es an einem Ring arbeitet (Zahlen Modulo 2 ^ N)
MSalters
9

Ich werde in dieser Antwort nicht auf das UB / nicht UB-Debakel eingehen.

Ich möchte nur darauf hinweisen 0und bin 0.0 anders, obwohl ich 0 == 0.0als wahr bewertet habe. 0ist ein intLiteral und 0.0ist ein doubleLiteral.

In diesem Fall ist das Endergebnis jedoch dasselbe: Es d/0ist eine Gleitkommadivision, da des doppelt ist und daher 0implizit in doppelt konvertiert wird.

Bolov
quelle
5
Ich sehe nicht, wie dies relevant ist, da die üblichen arithmetischen Konvertierungen angeben, dass das Teilen von a doubledurch ein intMittel, in das intkonvertiert wird double , und es in dem Standard angegeben ist, 0der in 0.0 (conv.fpint / 2) konvertiert
MM
@ MM das OP will wissen, ob 0es dasselbe ist wie0.0
Bolov
2
Die Frage lautet "Ist 0 != 0.0?". OP fragt nie, ob sie "gleich" sind. Es scheint mir auch, dass die Absicht der Frage darin besteht, ob d/0es sich anders verhalten kann alsd/0.0
MM
2
@ MM - Das OP hat gefragt . Sie zeigen mit diesen Konstantenänderungen keine wirklich anständige SO-Netiquette.
Geschichtenerzähler - Unslander Monica
7

Ich würde behaupten , dass foo/0und foo/0.0sind nicht gleich. Der resultierende Effekt der ersten (Ganzzahldivision oder Gleitkommadivision) hängt stark vom Typ ab foo, während dies für die zweite nicht gilt (es handelt sich immer um eine Gleitkommadivision).

Ob einer der beiden UB ist, spielt keine Rolle. Den Standard zitieren:

Das zulässige undefinierte Verhalten reicht vom vollständigen Ignorieren der Situation mit unvorhersehbaren Ergebnissen über das Verhalten während der Übersetzung oder Programmausführung in einer für die Umgebung charakteristischen dokumentierten Weise (mit oder ohne Ausgabe einer Diagnosemeldung) bis zum Beenden einer Übersetzung oder Ausführung (mit der Ausgabe) einer Diagnosemeldung).

(Hervorhebung von mir)

Beachten Sie die Warnung " Klammern um die als Wahrheitswert verwendete Zuweisung vorschlagen ": Sie können dem Compiler mitteilen, dass Sie das Ergebnis einer Zuweisung wirklich verwenden möchten, indem Sie explizit angeben und Klammern um die Zuweisung hinzufügen. Die resultierende Anweisung hat den gleichen Effekt, teilt dem Compiler jedoch mit, dass Sie wissen, was Sie tun. Das Gleiche gilt für foo/0.0: Da Sie dem Compiler explizit "Dies ist eine Gleitkommadivision" mitteilen, indem Sie 0.0stattdessen verwenden 0, vertraut der Compiler Ihnen und gibt keine Warnung aus.

Cássio Renan
quelle
1
Beide müssen die üblichen arithmetischen Konvertierungen durchlaufen , um sie zu einem gemeinsamen Typ zu bringen, wodurch in beiden Fällen eine Gleitkommadivision verbleibt.
Shafik Yaghmour
@ShafikYaghmour Du hast den Punkt in der Antwort verpasst. Beachten Sie, dass ich nie erwähnt habe, was die Art von ist foo. Das ist beabsichtigt. Ihre Behauptung ist nur in dem Fall wahr, dass fooes sich um einen Gleitkommatyp handelt.
Cássio Renan
Ich habe es nicht getan, der Compiler hat Typinformationen und versteht Konvertierungen. Vielleicht kann ein textbasierter statischer Analysator von solchen Dingen erfasst werden, aber der Compiler sollte dies nicht tun.
Shafik Yaghmour
Mein Punkt ist, dass ja, der Compiler die üblichen arithmetischen Konvertierungen kennt, aber keine Warnung ausgibt, wenn der Programmierer explizit ist. Der springende Punkt ist, dass dies wahrscheinlich kein Fehler ist, sondern ein absichtliches Verhalten.
Cássio Renan
Dann ist die Dokumentation, auf die ich hingewiesen habe, falsch, da beide Fälle eine Gleitkommadivision sind. Entweder ist die Dokumentation falsch oder die Diagnose hat einen Fehler.
Shafik Yaghmour
4

Dies sieht aus wie ein gcc-Fehler, die Dokumentation für -Wno-div-by-zero sagt eindeutig :

Warnen Sie nicht vor einer Ganzzahldivision zur Kompilierungszeit durch Null. Eine Gleitkommadivision durch Null wird nicht gewarnt, da dies ein legitimer Weg sein kann, um Unendlichkeiten und NaNs zu erhalten.

und nach den üblichen arithmetischen Konvertierungen, die in [expr.arith.conv] behandelt werden, sind beide Operanden doppelt :

Viele binäre Operatoren, die Operanden vom Typ Arithmetik oder Aufzählung erwarten, verursachen Konvertierungen und ergeben auf ähnliche Weise Ergebnistypen. Der Zweck besteht darin, einen gemeinsamen Typ zu erhalten, der auch der Typ des Ergebnisses ist. Dieses Muster wird als übliche arithmetische Konvertierung bezeichnet, die wie folgt definiert ist:

...

- Wenn einer der Operanden doppelt ist, wird der andere in double konvertiert.

und [expr.mul] :

Die Operanden von * und / oder müssen einen arithmetischen oder nicht skalierten Aufzählungstyp haben. Die Operanden von% müssen einen ganzzahligen oder nicht skalierten Aufzählungstyp haben. Die üblichen arithmetischen Konvertierungen werden an den Operanden durchgeführt und bestimmen den Typ des Ergebnisses.

In Bezug darauf, ob die Gleitkommadivision durch Null ein undefiniertes Verhalten ist und wie unterschiedliche Implementierungen damit umgehen, scheint mir hier meine Antwort zu sein . TL; DR; Es sieht so aus, als ob gcc mit Anhang F der Gleitkommadivision durch Null übereinstimmt, daher spielt undefiniert hier keine Rolle. Die Antwort wäre anders für Klirren.

Shafik Yaghmour
quelle
2

Die Gleitkommadivision durch Null verhält sich anders als die ganzzahlige Division durch Null.

Der IEEE-Gleitkomma- Standard unterscheidet zwischen + inf und -inf, während Ganzzahlen nicht unendlich speichern können. Das Ergebnis der Ganzzahldivision durch Null ist ein undefiniertes Verhalten. Die Gleitkommadivision durch Null wird durch den Gleitkommastandard definiert und führt zu + inf oder -inf.

Rizwan
quelle
2
Dies ist wahr, aber es ist nicht klar, wie dies für die Frage relevant ist, da in beiden Fällen eine Gleitkommadivision durchgeführt wird . Es gibt keine Ganzzahldivision im OP-Code.
Konrad Rudolph