Warum funktioniert NaN - NaN == 0.0 mit dem Intel C ++ - Compiler?

300

Es ist bekannt, dass sich NaNs in der Arithmetik ausbreiten, aber ich konnte keine Demonstrationen finden, deshalb schrieb ich einen kleinen Test:

#include <limits>
#include <cstdio>

int main(int argc, char* argv[]) {
    float qNaN = std::numeric_limits<float>::quiet_NaN();

    float neg = -qNaN;

    float sub1 = 6.0f - qNaN;
    float sub2 = qNaN - 6.0f;
    float sub3 = qNaN - qNaN;

    float add1 = 6.0f + qNaN;
    float add2 = qNaN + qNaN;

    float div1 = 6.0f / qNaN;
    float div2 = qNaN / 6.0f;
    float div3 = qNaN / qNaN;

    float mul1 = 6.0f * qNaN;
    float mul2 = qNaN * qNaN;

    printf(
        "neg: %f\nsub: %f %f %f\nadd: %f %f\ndiv: %f %f %f\nmul: %f %f\n",
        neg, sub1,sub2,sub3, add1,add2, div1,div2,div3, mul1,mul2
    );

    return 0;
}

Das Beispiel ( hier live laufen ) liefert im Grunde das, was ich erwarten würde (das Negative ist etwas seltsam, aber es macht irgendwie Sinn):

neg: -nan
sub: nan nan nan
add: nan nan
div: nan nan nan
mul: nan nan

MSVC 2015 produziert etwas Ähnliches. Intel C ++ 15 produziert jedoch:

neg: -nan(ind)
sub: nan nan 0.000000
add: nan nan
div: nan nan nan
mul: nan nan

Speziell, qNaN - qNaN == 0.0 .

Das ... kann nicht richtig sein, oder? Was sagen die relevanten Standards (ISO C, ISO C ++, IEEE 754) dazu und warum gibt es einen Unterschied im Verhalten zwischen den Compilern?

imallett
quelle
18
Javascript und Python (numpy) haben dieses Verhalten nicht. Nan-NaNist NaN. Perl und Scala verhalten sich ebenfalls ähnlich.
Paul
33
Vielleicht haben Sie unsichere mathematische Optimierungen aktiviert (das Äquivalent zu -ffast-mathon gcc)?
Matteo Italia
5
@nm: Nicht wahr. Anhang F, die optional aber normativ ist , wenn unterstützt, und notwendige Fließkomma Verhalten angegeben hat überhaupt , umfasst im Wesentlichen IEEE 754 in C
R .. GitHub STOPP HILFT ICE
5
Wenn Sie nach dem IEEE 754-Standard fragen möchten, erwähnen Sie ihn irgendwo in der Frage.
n. 'Pronomen' m.
68
Ich war mir sicher, dass es bei dieser Frage um JavaScript aus dem Titel ging.
MikeTheLiar

Antworten:

300

Der Standard - Gleitkomma - Handling in Intel C ++ Compiler ist /fp:fast, die Griffe NaN‚s unsafely (die auch Ergebnisse in NaN == NaNsind truezum Beispiel). Versuchen Sie, /fp:strictoder anzugeben, /fp:preciseund prüfen Sie, ob dies hilfreich ist.

Petr Abdulin
quelle
15
Ich habe es nur selbst versucht. In der Tat behebt die Angabe von präzisen oder strengen Angaben das Problem.
Imallett
67
Ich möchte Intels Entscheidung unterstützen, standardmäßig Folgendes festzulegen /fp:fast: Wenn Sie etwas Sicheres wollen , sollten Sie wahrscheinlich besser vermeiden, dass NaNs überhaupt auftauchen, und im Allgemeinen nicht ==mit Gleitkommazahlen verwenden. Sich auf die seltsame Semantik zu verlassen, die IEEE754 NaN zuweist, ist problematisch.
links um den
10
@leftaroundabout: Was findest du komisch an NaN, abgesehen von der IMHO schrecklichen Entscheidung, NaN! = NaN wahr zurückgeben zu lassen?
Supercat
21
NaNs haben wichtige Verwendungszwecke - sie können Ausnahmesituationen erkennen, ohne dass nach jeder Berechnung Tests erforderlich sind. Nicht jeder Gleitkommaentwickler benötigt sie, aber entlassen Sie sie nicht.
Bruce Dawson
6
@supercat Stimmen Sie aus Neugier der Entscheidung zu, NaN==NaNzurückzukehren false?
Kyle Strand
53

Dies . . . kann nicht richtig sein, richtig? Meine Frage: Was sagen die relevanten Normen (ISO C, ISO C ++, IEEE 754) dazu?

Petr Abdulin hat bereits geantwortet, warum der Compiler a gibt 0.0 Antwort .

Folgendes sagt IEEE-754: 2008:

(6.2 Operationen mit NaNs) "[...] Wenn für eine Operation mit leisen NaN-Eingängen außer Maximal- und Minimaloperationen ein Gleitkomma-Ergebnis geliefert werden soll, muss das Ergebnis ein leises NaN sein, das eines der folgenden sein sollte NaNs eingeben. "

Das einzig gültige Ergebnis für die Subtraktion von zwei leisen NaN-Operanden ist also ein leises NaN; Jedes andere Ergebnis ist ungültig.

Der C-Standard sagt:

(C11, F.9.2 Expressionstransformationen p1) "[...]

x - x → 0. 0 "Die Ausdrücke x - x und 0. 0 sind nicht äquivalent, wenn x ein NaN oder unendlich ist"

(wobei hier NaN ein leises NaN gemäß F.2.1p1 bezeichnet "Diese Spezifikation definiert nicht das Verhalten der Signalisierung von NaNs. Sie verwendet im Allgemeinen den Begriff NaN, um ruhige NaNs zu bezeichnen")

ouah
quelle
20

Da ich eine Antwort sehe, die die Einhaltung der Standards des Intel-Compilers in Frage stellt, und niemand anderes dies erwähnt hat, möchte ich darauf hinweisen, dass sowohl GCC als auch Clang einen Modus haben, in dem sie etwas ganz Ähnliches tun. Ihr Standardverhalten ist IEEE-konform -

$ g++ -O2 test.cc && ./a.out 
neg: -nan
sub: nan nan nan
add: nan nan
div: nan nan nan
mul: nan nan

$ clang++ -O2 test.cc && ./a.out 
neg: -nan
sub: -nan nan nan
add: nan nan
div: nan nan nan
mul: nan nan

- aber wenn Sie auf Kosten der Korrektheit nach Geschwindigkeit fragen, bekommen Sie, wonach Sie fragen -

$ g++ -O2 -ffast-math test.cc && ./a.out 
neg: -nan
sub: nan nan 0.000000
add: nan nan
div: nan nan 1.000000
mul: nan nan

$ clang++ -O2 -ffast-math test.cc && ./a.out 
neg: -nan
sub: -nan nan 0.000000
add: nan nan
div: nan nan nan
mul: nan nan

Ich denke, es ist völlig fair, die Wahl des ICC als Standard zu kritisieren , aber ich würde nicht die gesamten Unix-Kriege in diese Entscheidung zurücklesen.

zwol
quelle
Beachten Sie, dass mit -ffast-math, gcceinhält nicht nach ISO 9899: 2011 in Bezug auf arithmetische Gleitkomma mehr.
Fuz
1
@FUZxxl Ja, der Punkt ist, dass beide Compiler einen nicht kompatiblen Gleitkommamodus haben. Es ist nur so, dass icc standardmäßig diesen Modus verwendet und gcc nicht.
zwol
4
Nur um Kraftstoff ins Feuer zu werfen, mag ich Intels Wahl, standardmäßig schnelles Rechnen zu ermöglichen. Bei der Verwendung von Floats geht es vor allem darum, einen hohen Durchsatz zu erzielen.
Navin