Pourquoi NaN - NaN == 0.0 avec le compilateur Intel C ++?

300

Il est bien connu que les NaN se propagent en arithmétique, mais je n'ai trouvé aucune démonstration, j'ai donc écrit un petit 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;
}

L'exemple (en cours d'exécution ici ) produit essentiellement ce à quoi je m'attendais (le négatif est un peu bizarre, mais cela a du sens):

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

MSVC 2015 produit quelque chose de similaire. Cependant, Intel C ++ 15 produit:

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

Plus précisément, qNaN - qNaN == 0.0.

Cela ... ne peut pas être vrai, non? Que disent les normes pertinentes (ISO C, ISO C ++, IEEE 754) à ce sujet, et pourquoi y a-t-il une différence de comportement entre les compilateurs?

imallett
la source
18
Javascript et Python (numpy) n'ont pas ce comportement. Nan-NaNest NaN. Perl et Scala se comportent également de la même manière.
Paul
33
Peut-être avez-vous activé des optimisations mathématiques dangereuses (l'équivalent de -ffast-mathsur gcc)?
Matteo Italia
5
@nm: Pas vrai. L'annexe F, qui est facultative mais normative lorsqu'elle est prise en charge, et nécessaire pour avoir un comportement en virgule flottante spécifié du tout , incorpore essentiellement IEEE 754 dans C.
R .. GitHub STOP HELPING ICE
5
Si vous souhaitez poser des questions sur la norme IEEE 754, mentionnez-la quelque part dans la question.
n. «pronoms» m.
68
J'étais sûr que cette question concernait JavaScript du titre.
MikeTheLiar

Réponses:

300

La valeur par défaut à virgule flottante de manipulation dans le compilateur Intel C /fp:fast, qui traite de NaNl » unsafely (qui a également conduit à NaN == NaNêtre truepar exemple). Essayez de spécifier /fp:strictou /fp:preciseet voyez si cela aide.

Petr Abdulin
la source
15
J'essayais juste moi-même. En effet, la spécification soit précise soit stricte corrige le problème.
imallett
67
Je voudrais approuver la décision d'Intel de choisir par défaut /fp:fast: si vous voulez quelque chose de sûr , vous devriez probablement éviter de voir des NaN apparaître en premier lieu, et ne les utilisez généralement pas ==avec des nombres à virgule flottante. S'appuyer sur la sémantique bizarre que IEEE754 assigne à NaN demande des ennuis.
leftaroundabout
10
@leftaroundabout: Qu'est-ce que vous trouvez bizarre à propos de NaN, à part la décision horrible à mon humble avis d'avoir NaN! = NaN retour vrai?
supercat
21
Les NaN ont des utilisations importantes - ils peuvent détecter des situations exceptionnelles sans nécessiter de tests après chaque calcul. Tous les développeurs en virgule flottante n'en ont pas besoin, mais ne les rejetez pas.
Bruce Dawson
6
@supercat Par curiosité, êtes-vous d'accord avec la décision de NaN==NaNrevenir false?
Kyle Strand
53

Ce . . . ne peut pas avoir raison, non? Ma question: qu'en disent les normes pertinentes (ISO C, ISO C ++, IEEE 754)?

Petr Abdulin a déjà répondu pourquoi le compilateur donne une 0.0réponse.

Voici ce que dit IEEE-754: 2008:

(6.2 Opérations avec NaN) "[...] Pour une opération avec des entrées de NaN silencieuses, autres que les opérations maximales et minimales, si un résultat à virgule flottante doit être fourni, le résultat doit être un NaN silencieux qui doit être l'un des entrée NaNs. "

Ainsi, le seul résultat valide pour la soustraction de deux opérandes NaN silencieux est un NaN silencieux; tout autre résultat n'est pas valide.

La norme C dit:

(C11, F.9.2 Transformations d'expression p1) "[...]

x - x → 0. 0 "Les expressions x - x et 0. 0 ne sont pas équivalentes si x est NaN ou infini"

(où ici NaN désigne un NaN silencieux selon F.2.1p1 "Cette spécification ne définit pas le comportement des NaN de signalisation. Elle utilise généralement le terme NaN pour désigner des NaN silencieux")

ouah
la source
20

Puisque je vois une réponse contestant la conformité aux normes du compilateur d'Intel, et personne d'autre ne l'a mentionné, je soulignerai que GCC et Clang ont un mode dans lequel ils font quelque chose de très similaire. Leur comportement par défaut est conforme IEEE -

$ 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

- mais si vous demandez de la vitesse au détriment de l'exactitude, vous obtenez ce que vous demandez -

$ 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

Je pense qu'il est tout à fait juste de critiquer le choix par défaut d'ICC , mais je ne relirais pas l'intégralité des guerres Unix dans cette décision.

zwol
la source
Notez qu'avec -ffast-math, gccn'est plus conforme à ISO 9899: 2011 en ce qui concerne l'arithmétique à virgule flottante.
fuz
1
@FUZxxl Oui, le fait est que les deux compilateurs ont un mode à virgule flottante non conforme, c'est juste que icc utilise par défaut ce mode et gcc non.
zwol
4
Juste pour jeter du carburant dans le feu, j'aime vraiment le choix d'Intel pour activer les mathématiques rapides par défaut. L'intérêt de l'utilisation de flotteurs est d'obtenir un débit élevé.
Navin