¿Por qué NaN - NaN == 0.0 con el compilador Intel C ++?

300

Es bien sabido que los NaN se propagan en aritmética, pero no pude encontrar ninguna demostración, así que escribí una pequeña prueba:

#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;
}

El ejemplo ( correr en vivo aquí ) produce básicamente lo que esperaría (lo negativo es un poco extraño, pero tiene sentido):

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

MSVC 2015 produce algo similar. Sin embargo, Intel C ++ 15 produce:

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

Específicamente, qNaN - qNaN == 0.0.

Esto ... no puede estar bien, ¿verdad? ¿Qué dicen los estándares relevantes (ISO C, ISO C ++, IEEE 754) sobre esto, y por qué hay una diferencia de comportamiento entre los compiladores?

imallett
fuente
18
Javascript y Python (numpy) no tienen este comportamiento. Nan-NaNes NaN. Perl y Scala también se comportan de manera similar.
Paul
33
¿Quizás ha habilitado optimizaciones matemáticas inseguras (el equivalente de -ffast-mathen gcc)?
Matteo Italia
55
@nm: No es cierto. Anexo F, que es opcional pero normativo cuando está apoyado, y necesario para tener un comportamiento de punto flotante especificado en absoluto , esencialmente incorpora IEEE 754 en C.
R .. GitHub dejar de ayudar a ICE
55
Si desea preguntar sobre el estándar IEEE 754, menciónelo en alguna parte de la pregunta.
n. 'pronombres' m.
68
Estaba seguro de que esta pregunta era sobre JavaScript del título.
MikeTheLiar

Respuestas:

300

El manejo predeterminado de coma flotante en el compilador Intel C ++ es /fp:fast, que maneja NaNde forma insegura (lo que también resulta NaN == NaNser, truepor ejemplo). Intente especificar /fp:stricto /fp:precisey vea si eso ayuda.

Petr Abdulin
fuente
15
Solo estaba intentando esto yo mismo. De hecho, la especificación de soluciones precisas o estrictas soluciona el problema.
imallett
67
Me gustaría respaldar la decisión de Intel de cambiar por defecto a /fp:fast: si desea algo seguro , probablemente debería evitar mejor que aparezcan NaN en primer lugar y, en general, no usar ==con números de punto flotante. Confiar en la extraña semántica que IEEE754 asigna a NaN es pedir problemas.
Leftaroundabout
10
@leftaroundabout: ¿Qué le parece extraño sobre NaN, aparte de la horrible decisión de IMHO de que NaN! = NaN devuelva verdadero?
supercat
21
Los NaN tienen usos importantes: pueden detectar situaciones excepcionales sin requerir pruebas después de cada cálculo. No todos los desarrolladores de punto flotante los necesitan, pero no los descarte.
Bruce Dawson
66
@supercat Por curiosidad, ¿estás de acuerdo con la decisión de NaN==NaNregresar false?
Kyle Strand
53

Esta . . . no puede estar bien, ¿verdad? Mi pregunta: ¿qué dicen los estándares relevantes (ISO C, ISO C ++, IEEE 754) sobre esto?

Petr Abdulin ya respondió por qué el compilador da una 0.0respuesta.

Esto es lo que dice IEEE-754: 2008:

(6.2 Operaciones con NaNs) "[...] Para una operación con entradas de NaN silenciosas, distintas de las operaciones máximas y mínimas, si se va a entregar un resultado de coma flotante, el resultado será un NaN silencioso que debería ser uno de los ingrese NaNs ".

Entonces, el único resultado válido para la resta de dos operandos de NaN silenciosos es un NaN silencioso; Cualquier otro resultado no es válido.

El estándar C dice:

(C11, F.9.2 Transformaciones de expresión p1) "[...]

x - x → 0. 0 "Las expresiones x - x y 0. 0 no son equivalentes si x es un NaN o infinito"

(donde aquí NaN denota un NaN silencioso según F.2.1p1 "Esta especificación no define el comportamiento de la señalización de NaN. Generalmente usa el término NaN para denotar NaN silencioso")

ouah
fuente
20

Como veo una respuesta que impugna el cumplimiento de los estándares del compilador de Intel, y nadie más ha mencionado esto, señalaré que tanto GCC como Clang tienen un modo en el que hacen algo bastante similar. Su comportamiento predeterminado es compatible con 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

- pero si pides velocidad a expensas de la corrección, obtienes lo que pides -

$ 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

Creo que es completamente justo criticar la elección de incumplimiento de ICC , pero no leería toda la guerra de Unix en esa decisión.

zwol
fuente
Tenga en cuenta que con -ffast-math, gccya no cumple con ISO 9899: 2011 con respecto a la aritmética de coma flotante.
fuz
1
@FUZxxl Sí, el punto es que ambos compiladores tienen un modo de punto flotante no compatible, es solo que icc por defecto es ese modo y gcc no.
zwol
44
Solo para arrojar combustible al fuego, realmente me gusta la elección de Intel de habilitar las matemáticas rápidas por defecto. El objetivo de usar flotadores es obtener un alto rendimiento.
Navin