Perché NaN - NaN == 0.0 con il compilatore Intel C ++?


300

È noto che i NaN si propagano in aritmetica, ma non sono riuscito a trovare alcuna dimostrazione, quindi ho scritto un piccolo 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'esempio ( correre dal vivo qui ) produce sostanzialmente ciò che mi aspetterei (il negativo è un po 'strano, ma ha un senso):

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

MSVC 2015 produce qualcosa di simile. Tuttavia, Intel C ++ 15 produce:

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

Specificamente, qNaN - qNaN == 0.0.

Questo ... non può essere giusto, vero? Cosa dicono gli standard pertinenti (ISO C, ISO C ++, IEEE 754) su questo, e perché c'è una differenza nel comportamento tra i compilatori?


18
Javascript e Python (numpy) non hanno questo comportamento. Nan-NaNlo è NaN. Anche Perl e Scala si comportano in modo simile.
Paul,

33
Forse hai abilitato ottimizzazioni matematiche non sicure (l'equivalente di -ffast-mathsu gcc)?
Matteo Italia,

5
@nm: non vero. Allegato F, che è opzionale ma normativa quando supportato, e necessario avere un comportamento in virgola mobile specificato affatto , comprende essenzialmente IEEE 754 in C.
R .. GitHub smettere di aiutare ICE

5
Se vuoi chiedere informazioni sullo standard IEEE 754, menzionalo da qualche parte nella domanda.
n. 'pronomi' m.

68
Ero sicuro che questa domanda riguardasse JavaScript dal titolo.
MikeTheLiar,

Risposte:


300

La gestione predefinita in virgola mobile nel compilatore Intel C ++ è /fp:fast, che gestisce in modo NaNnon sicuro (che risulta anche NaN == NaNessere truead esempio). Prova a specificare /fp:stricto /fp:precisee vedi se aiuta.


15
Ci stavo provando da solo. In effetti, specificare il problema in modo preciso o rigoroso.
imallett,

67
Vorrei appoggiare la decisione di Intel di default /fp:fast: se vuoi qualcosa di sicuro , dovresti probabilmente evitare in primo luogo la comparsa di NaN e in genere non utilizzare ==con numeri in virgola mobile. Affidarsi alla strana semantica che IEEE754 assegna a NaN richiede problemi.
lasciato il

10
@leftaroundabout: Cosa trovi strano su NaN, a parte l'orrenda decisione dell'IMHO di far sì che NaN! = NaN ritorni vero?
supercat

21
Le NaN hanno usi importanti: possono rilevare situazioni eccezionali senza richiedere test dopo ogni calcolo. Non tutti gli sviluppatori in virgola mobile ne hanno bisogno ma non li ignorano.
Bruce Dawson,

6
@supercat Per curiosità, sei d'accordo con la decisione di NaN==NaNtornare false?
Kyle Strand,

53

Questo . . . non può essere giusto, vero? La mia domanda: cosa dicono gli standard pertinenti (ISO C, ISO C ++, IEEE 754) al riguardo?

Petr Abdulin ha già risposto perché il compilatore dà una 0.0risposta.

Ecco cosa dice IEEE-754: 2008:

(6.2 Operazioni con NaNs) "[...] Per un'operazione con ingressi NaN silenziosi, oltre alle operazioni massime e minime, se si vuole fornire un risultato in virgola mobile il risultato deve essere un NaN silenzioso che dovrebbe essere uno dei input NaNs. "

Quindi l'unico risultato valido per la sottrazione di due operandi NaN silenziosi è un NaN silenzioso; qualsiasi altro risultato non è valido.

Lo standard C dice:

(C11, F.9.2 Trasformazioni di espressioni p1) "[...]

x - x → 0. 0 "Le espressioni x - x e 0. 0 non sono equivalenti se x è un NaN o infinito"

(dove qui NaN indica un NaN silenzioso secondo F.2.1p1 "Questa specifica non definisce il comportamento della segnalazione di NaN. In genere usa il termine NaN per indicare NaN silenziosi")


20

Dal momento che vedo una risposta che impone la conformità agli standard del compilatore Intel, e nessun altro lo ha menzionato, farò notare che sia GCC che Clang hanno una modalità in cui fanno qualcosa di abbastanza simile. Il loro comportamento predefinito è 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

- ma se chiedi velocità a scapito della correttezza, ottieni ciò che chiedi -

$ 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

Penso che sia del tutto giusto criticare la scelta del default di ICC , ma non rileggerei l'intera guerra Unix in quella decisione.


Si noti che con -ffast-math, gccnon è più conforme alla norma ISO 9899: 2011 rispetto all'aritmetica in virgola mobile.
fuz,

1
@FUZxxl Sì, il punto è che entrambi i compilatori hanno una modalità a virgola mobile non conforme, è solo che icc è impostato su quella modalità e gcc no.
zwol,

4
Solo per gettare benzina nel fuoco, mi piace molto la scelta di Intel di abilitare la matematica veloce per impostazione predefinita. Il punto centrale dell'utilizzo dei float è ottenere un throughput elevato.
Navin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.