web-dev-qa-db-fra.com

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

Il est bien connu que les NaN se propagent en arithmétique, mais comme je n’ai trouvé aucune démonstration, j’ai é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 ( running live here ) produit fondamentalement ce à quoi je m'attendais (le négatif est un peu bizarre, mais cela a un 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.

Ça ... ça ne peut pas être vrai, non? Que disent les normes concernées (ISO C, ISO C++, IEEE 754) et pourquoi existe-t-il une différence de comportement entre les compilateurs?

297
imallett

La valeur par défaut du compilateur Intel C++ est le _/fp:fast_, qui gère les NaN de manière non sécurisée (ce qui a également pour résultat que _NaN == NaN_ est true, par exemple). Essayez de spécifier _/fp:strict_ ou _/fp:precise_ et voyez si cela vous aide.

296
Petr Abdulin

Ceci.... Ne peut pas être correct, non? Ma question: que disent les normes pertinentes (ISO C, ISO C++, IEEE 754)?

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

Voici ce que dit IEEE-754: 2008:

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

Ainsi, le seul résultat valide pour la soustraction de deux opérandes de 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 un NaN ou une infinie"

(où NaN désigne ici un NaN calme 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")

52
ouah

Puisque je vois une réponse qui remet en cause la conformité aux normes du compilateur d’Intel et que personne d’autre n’a mentionné cela, je ferai remarquer que GCC et Clang ont tous deux un mode dans lequel ils font quelque chose de très similaire. Leur comportement par défaut est conforme à l'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 rapidité au détriment de la correction, 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 de la CCI de défaut, mais je ne relirais pas l'intégralité de la guerre Unix dans cette décision.

19
zwol