Verifica se un double (o float) è NaN in C ++


369

Esiste una funzione isnan ()?

PS .: Sono in MinGW (se questo fa la differenza).

Ho risolto questo problema usando isnan () di <math.h>, che non esiste <cmath>, in cui stavo inizialmente lavorando #include.


2
Non sono puro, puoi farlo in modo portabile. Chi dice che C ++ richiede IEEE754?
David Heffernan,


Solo una nota, 1 oz di prevenzione è meglio di 1 libbra di cura. In altre parole, impedire che 0.f / 0.f venga mai eseguito è molto meglio che controllare retroattivamente per nan'nel tuo codice. nanQuesto può essere terribilmente distruttivo per il tuo programma, se permesso di proliferare può introdurre bug difficili da trovare. Questo perché nanè tossico, (5 * nan= nan), nannon è uguale a nulla ( nan! = nan), nanNon è più grande di niente ( nan!> 0), nannon è inferiore a nulla ( nan! <0).
bobobobo,

1
@bobobobo: questa è una funzione che consente il controllo centralizzato degli errori. Proprio come le eccezioni rispetto ai valori di ritorno.
Ben Voigt,

2
Perché <cmath> non ha isnan ()? È in std ::
frankliuao il

Risposte:


351

Secondo lo standard IEEE, i valori NaN hanno la proprietà dispari che i confronti che li coinvolgono sono sempre falsi. Cioè, per un float f, f != fsarà vero solo se f è NaN.

Si noti che, come sottolineato da alcuni commenti di seguito, non tutti i compilatori lo rispettano quando si ottimizza il codice.

Per qualsiasi compilatore che afferma di utilizzare il punto in virgola mobile IEEE, questo trucco dovrebbe funzionare. Ma non posso garantire che sarà funzionare in pratica. Verificare con il compilatore, in caso di dubbi.


4
È meglio che il compilatore non lo rimuova se eseguito in modalità IEEE. Controlla la documentazione per il tuo compilatore, ovviamente ...
dmckee --- ex-moderatore gattino

38
-1 funziona solo in teoria, non in pratica: compilatori come g ++ (con -fastmath) rovinano tutto. l'unico modo generale, fino a c ++ 0x, è testare bitpattern.
Saluti e hth. - Alf

66
@Alf: la documentazione per l' -ffast-mathopzione dice esplicitamente che può provocare un output errato per i programmi che dipendono da un'implementazione esatta se regole / specifiche IEEE o ISO per le funzioni matematiche. Senza tale opzione abilitata, l'utilizzo x != xè un modo perfettamente valido e portatile di test per NaN.
Adam Rosenfield,

7
@Adam: la documentazione afferma apertamente che non è conforme, sì. e sì, ho già incontrato questo argomento prima, discutendone a lungo con Gabriel Dos Reis. è comunemente usato per difendere il design, in una discussione circolare (non so se tu intendessi associarlo a questo, ma vale la pena conoscerlo - è roba da guerra alla fiamma). la tua conclusione che x != xè valida senza quell'opzione non segue logicamente. potrebbe essere vero per una particolare versione di g ++ o no. comunque, generalmente non hai modo di garantire che l'opzione fastmath non verrà utilizzata.
Saluti e hth. - Alf

7
@Alf: No, non ero a conoscenza della tua discussione con Gabriel Dos Reis. Steve Jessop ha fatto un grande punto nell'altra domanda sull'assunzione della rappresentazione IEEE. Se si assume IEEE 754 e che il compilatore funziona in modo conforme (cioè senza l' -ffast-mathopzione), allora x != xè una soluzione valida e portatile. Puoi anche testare -ffast-mathtestando la __FAST_MATH__macro e passare a un'implementazione diversa in quel caso (ad es. Usa i sindacati e il bit twiddling).
Adam Rosenfield,

220

Non è isnan()disponibile alcuna funzione nella libreria standard C ++ corrente. È stato introdotto in C99 e definito come una macro, non una funzione. Gli elementi della libreria standard definita da C99 non fanno parte dell'attuale standard C ++ ISO / IEC 14882: 1998 né del suo aggiornamento ISO / IEC 14882: 2003.

Nel 2005 è stato proposto il rapporto tecnico 1. Il TR1 porta la compatibilità con C99 a C ++. Nonostante non sia mai stato adottato ufficialmente per diventare lo standard C ++, molti (le implementazioni GCC 4.0+ o Visual C ++ 9.0+ C ++ forniscono funzionalità TR1, tutte o solo alcune (Visual C ++ 9.0 non fornisce funzioni matematiche C99) .

Se TR1 è disponibile, allora cmathinclude elementi C99 come isnan(), isfinite(), ecc, ma essi sono definiti come funzioni, non le macro, di solito in std::tr1::spazio dei nomi, anche se molte implementazioni (ad esempio GCC 4+ su Linux o in XCode su Mac OS X 10.5 e versioni successive) iniettare i loro direttamente a std::, quindi std::isnanè ben definito.

Inoltre, alcune implementazioni di C ++ rendono ancora isnan()disponibile la macro C99 per C ++ (inclusa tramite cmatho math.h), ciò che può causare più confusioni e gli sviluppatori possono supporre che sia un comportamento standard.

Una nota su Viusal C ++, come menzionato sopra, non fornisce std::isnannessuno dei due std::tr1::isnan, ma fornisce una funzione di estensione definita come _isnan()disponibile da Visual C ++ 6.0

Su XCode, c'è ancora più divertimento. Come accennato, GCC 4+ definisce std::isnan. Per le versioni precedenti del compilatore e della libreria in formato XCode, sembra (qui c'è una discussione pertinente ), non ho avuto la possibilità di controllarmi) sono definite due funzioni, __inline_isnand()su Intel e __isnand()su Power PC.


21
Tutti vogliono queste funzioni come isNan o isInfinity. Perché i responsabili non includono semplicemente nei loro standard ???? - Cercherò di scoprire come prendere il comando e di esprimere il mio voto per questo. Sul serio.
shuhalo,

8
@shuhalo In carica ancora?
Tomáš Zato - Ripristina Monica l'

11
Questa risposta dovrebbe essere aggiornata poiché std::isnanfa ora parte dello standard C ++ 11 e il supporto è stato esteso. std :: isnan è stato implementato in Visual Studio a partire da Visual Studio 2013. Forse @shuhalo è diventato responsabile :-)
aberaud

170

Prima soluzione: se si utilizza C ++ 11

Da quando è stato chiesto, ci sono stati alcuni nuovi sviluppi: è importante sapere che std::isnan()fa parte di C ++ 11

Sinossi

Definito nell'intestazione <cmath>

bool isnan( float arg ); (since C++11)
bool isnan( double arg ); (since C++11)
bool isnan( long double arg ); (since C++11)

Determina se il dato numero in virgola mobile arg non è un numero ( NaN).

parametri

arg: valore in virgola mobile

Valore di ritorno

truese arg è NaN,false altrimenti

Riferimento

http://en.cppreference.com/w/cpp/numeric/math/isnan

Nota che questo è incompatibile con -fast-math se usi g ++, vedi sotto per altri suggerimenti.


Altre soluzioni: se si utilizzano strumenti non conformi a C ++ 11

Per C99, in C, questo è implementato come una macro isnan(c)che restituisce un valore int. Il tipo dix deve essere float, double o long double.

Vari fornitori possono includere o meno una funzione isnan() .

Il modo presumibilmente portatile per verificare NaNè usare la proprietà IEEE 754 che NaNnon è uguale a se stessa: cioè x == xsarà falsa per xessere NaN.

Tuttavia, l'ultima opzione potrebbe non funzionare con tutti i compilatori e alcune impostazioni (in particolare le impostazioni di ottimizzazione), quindi in ultima istanza, puoi sempre controllare il modello di bit ...


8
Merita sicuramente la risposta accettata e merita più voti. Grazie per la punta
LBes

3
-1 std::isnan è ancora una cattiva raccomandazione a partire da febbraio 2017, poiché non funziona con l'ottimizzazione in virgola mobile di g ++.
Saluti e hth. - Alf

@ Cheersandhth.-Alf: questa opzione è conforme IEEE? La risposta è stata modificata
BlueTrin,

@BlueTrin: entrambi x != xe isnansono tenuti a funzionare per la conformità IEEE 754. Riguardo a quest'ultimo, lo standard IEEE 754-2008 afferma che "Le implementazioni devono fornire le seguenti operazioni non computazionali per tutti i formati aritmetici supportati" e "isNaN (x) è vero se e solo se x è un NaN". Per verificare la conformità che lo standard richiede is754version1985()e is754version2008(), laddove invece offre C ++ std::numeric_limits<Fp>::is_iec559()(IEC 559 è lo stesso standard). Sfortunatamente con l' -ffast-mathottimizzazione, ad es. G ++ afferma la conformità ma non è conforme.
Saluti e hth. - Alf,

1
Attenzione: isnan (x) non funziona con l'opzione -ffinite-math-only in gcc e clang
A Fog

82

C'è anche una libreria di sola intestazione presente in Boost che ha strumenti accurati per gestire i tipi di dati in virgola mobile

#include <boost/math/special_functions/fpclassify.hpp>

Ottieni le seguenti funzioni:

template <class T> bool isfinite(T z);
template <class T> bool isinf(T t);
template <class T> bool isnan(T t);
template <class T> bool isnormal(T t);

Se hai tempo, dai un'occhiata a tutto il toolkit matematico di Boost, ha molti strumenti utili e sta crescendo rapidamente.

Inoltre, quando si ha a che fare con punti mobili e non mobili, potrebbe essere una buona idea guardare le conversioni numeriche .


1
Grazie! Proprio quello che stavo cercando.
Dr. Watson,

è stato aggiunto in Boost 1.35 (ho appena scoperto che il mio programma non si compila sulla vecchia distribuzione Linux).
marcin,

2
se si compila con l'opzione --fast-math, questa funzione non funzionerà come previsto.
Gaetano Mendola,

43

Ci sono tre modi "ufficiali": POSIX isnanmacro , C ++ 0x isnanmodello di funzione , o Visual C ++ _isnanfunzione .

Purtroppo è piuttosto poco pratico rilevare quale di quelli usare.

E sfortunatamente, non esiste un modo affidabile per rilevare se si dispone della rappresentazione IEEE 754 con NaNs. La biblioteca standard offre un modo ufficiale (numeric_limits<double>::is_iec559 ). Ma in pratica compilatori come g ++ rovinano tutto.

In teoria si potrebbe usare semplicemente x != x, ma compilatori come g ++ e visual c ++ lo rovinano.

Quindi, alla fine, prova i bitpattern NaN specifici , assumendo (e speriamo di far rispettare, a un certo punto!) Una rappresentazione particolare come IEEE 754.


EDIT : come esempio di "compilatori come g ++ ... rovina tutto", considera

#include <limits>
#include <assert.h>

void foo( double a, double b )
{
    assert( a != b );
}

int main()
{
    typedef std::numeric_limits<double> Info;
    double const nan1 = Info::quiet_NaN();
    double const nan2 = Info::quiet_NaN();
    foo( nan1, nan2 );
}

Compilazione con g ++ (TDM-2 mingw32) 4.4.1:

C: \ test> digitare "C: \ Programmi \ @commands \ gnuc.bat"
@rem -finput-charset = windows-1252
@ g ++ -O -pedantic -std = c ++ 98 -Wall -Wwrite-stringhe% * -Wno-long-long

C: \ test> gnuc x.cpp

C: \ test> a && echo funziona ... || echo! fallito
lavori...

C: \ test> gnuc x.cpp --fast-math

C: \ test> a && echo funziona ... || echo! fallito
Asserzione non riuscita: a! = B, file x.cpp, riga 6

Questa applicazione ha richiesto a Runtime di terminarlo in modo insolito.
Per ulteriori informazioni, contattare il team di supporto dell'applicazione.
! fallito

C: \ test> _

4
@Alf: il tuo esempio funziona come previsto su Mac OS X e Linux su varie versioni di g ++ tra 4.0 e 4.5. La documentazione per l' -ffast-mathopzione dice esplicitamente che può provocare un output errato per i programmi che dipendono da un'implementazione esatta se le regole / specifiche IEEE o ISO per le funzioni matematiche. Senza tale opzione abilitata, l'utilizzo x != xè un modo perfettamente valido e portatile di test per NaN.
Adam Rosenfield,

6
@Adam: Quello che ti manca è che lo standard C ++ non richiede la rappresentazione IEEE o la matematica per i float. Per quanto ti dice la pagina man, gcc -ffast-mathè ancora un'implementazione C ++ conforme (beh, supponendo che sia numeric_limits::is_iec559corretta, lo è, anche se Alf suggerisce sopra che non lo fa): il codice C ++ che si basa su IEEE non è C ++ portatile e non ha alcun diritto aspettarsi implementazioni per fornirlo.
Steve Jessop,

5
E Alf ha ragione, test rapido su gcc 4.3.4 ed is_iec559è vero con -ffast-math. Quindi il problema qui è che i documenti di GCC -ffast-mathdicono solo che non è IEEE / ISO per le funzioni matematiche, mentre dovrebbero dire che non è C ++, perché la sua implementazione di numeric_limitsè ridotta. Immagino che GCC non possa sempre dire al momento della definizione del modello, se l'eventuale backend ha effettivamente float conformi, e quindi non ci prova nemmeno. IIRC ci sono problemi simili nella lista dei bug in sospeso per la conformità C99 di GCC.
Steve Jessop,

1
@Alf, @Steve, non sapevo che lo standard C ++ non avesse specifiche sui valori in virgola mobile. È piuttosto scioccante per me. Sembra meglio gestire IEEE 754 e NaN come estensione specifica della piattaforma anziché standard. No? E posso aspettarmi qualsiasi tipo di isnan () o IEEE754 aggiunto in C ++ 0x?
Eonil,

3
@Eonil: C ++ 0x ha ancora per esempio "La rappresentazione del valore dei tipi a virgola mobile è definita dall'implementazione". C e C ++ mirano entrambi a supportare implementazioni su macchine senza hardware a virgola mobile e i float IEEE 754 corretti possono essere un po 'più lenti da emulare rispetto a alternative ragionevolmente accurate. La teoria è che puoi affermare is_iec559se hai bisogno dell'IEEE, in pratica che non sembra funzionare su GCC. C ++ 0x ha una isnanfunzione, ma dal momento che GCC non implementa correttamente is_iec559ora, immagino che non lo farà nemmeno in C ++ 0x e -ffast-mathpotrebbe anche romperne isnan.
Steve Jessop,

39

C'è uno std :: isnan se il compilatore supporta le estensioni c99, ma non sono sicuro che lo faccia mingw.

Ecco una piccola funzione che dovrebbe funzionare se il tuo compilatore non ha la funzione standard:

bool custom_isnan(double var)
{
    volatile double d = var;
    return d != d;
}

6
perché non solo var! = var?
Brian R. Bondy,

8
Quando lo fa loro è una possibilità che il compilatore ottimizzerà il confronto, restituendo sempre vero.
CTT

23
No non c'è. Un compilatore che lo fa è rotto. Potresti anche dire che c'è una possibilità che la libreria standard isnanrestituisca il risultato sbagliato. Tecnicamente vero, il compilatore potrebbe essere difettoso, ma in pratica, Not Gonna Happen. Come var != var. Funziona perché è così che vengono definiti i valori in virgola mobile IEEE.
jalf

29
se è impostato -ffast-math, isnan () non riuscirà a restituire il risultato corretto per gcc. Naturalmente, questa ottimizzazione è documentata come una rottura della semantica IEEE ...
Matthew Herrmann,

Se è impostato -ffast-math, il compilatore è difettoso. O meglio, se -ffast-math è impostato, tutte le scommesse sono disattivate e comunque non si può fare affidamento su NaN.
Adrian Ratnapala,

25

È possibile utilizzare numeric_limits<float>::quiet_NaN( )definito nella limitslibreria standard con cui testare. È stata definita una costante separata per double.

#include <iostream>
#include <math.h>
#include <limits>

using namespace std;

int main( )
{
   cout << "The quiet NaN for type float is:  "
        << numeric_limits<float>::quiet_NaN( )
        << endl;

   float f_nan = numeric_limits<float>::quiet_NaN();

   if( isnan(f_nan) )
   {
       cout << "Float was Not a Number: " << f_nan << endl;
   }

   return 0;
}

Non so se funziona su tutte le piattaforme, poiché ho testato solo con g ++ su Linux.


2
Attenzione però: sembra che ci sia un bug in numeric_limits in GCC versione 3.2.3, poiché restituisce 0.0 per quiet_NaN. Le versioni successive di GCC vanno bene per la mia esperienza.
Nathan Kitchen,

@ Nathan: Buono a sapersi. Sto usando la versione 4.3.2, quindi sono fuori dal bosco.
Bill the Lizard,

18

È possibile utilizzare la isnan()funzione, ma è necessario includere la libreria matematica C.

#include <cmath>

Poiché questa funzione fa parte di C99, non è disponibile ovunque. Se il fornitore non fornisce la funzione, è anche possibile definire la propria variante per la compatibilità.

inline bool isnan(double x) {
    return x != x;
}

Stavo usando <cmath> e non c'è isnan in esso! Tra l'altro ho scoperto che non v'è un isnanin <math.h>
Hasen

1
Come ho già detto, questo fa parte del C99. Poiché C99 non fa parte di alcuno standard C ++ attuale, ho fornito l'alternativa. Ma poiché è probabile che isnan () sarà incluso in un prossimo standard C ++, ho messo una direttiva #ifndef attorno ad esso.
Raimue del

12

Il codice seguente utilizza la definizione di NAN (tutti i bit di esponente impostati, almeno un set di bit frazionario) e presuppone che sizeof (int) = sizeof (float) = 4. È possibile cercare NAN in Wikipedia per i dettagli.

bool IsNan( float value ) { return ((*(UINT*)&value) & 0x7fffffff) > 0x7f800000; }


Credo che funzionerebbe anche su piattaforme big endian. Il letterale 0x7fffffffsarebbe semplicemente seduto nella memoria come ff ff ff 7f. valueha lo stesso ordinamento 0x7f800000, quindi tutte le operazioni si allineano (non c'è scambio di byte). Sarei interessato se qualcuno potesse testarlo su una grande piattaforma endian.
Bryan W. Wagner,

0x7fff1234è anche una NaN. Così è0xffffffff
Steve Hollasch il

12

prevenzione nan

La mia risposta a questa domanda è non usare controlli retroattivi pernan . Utilizzare invece i controlli preventivi per le divisioni del modulo 0.0/0.0.

#include <float.h>
float x=0.f ;             // I'm gonna divide by x!
if( !x )                  // Wait! Let me check if x is 0
  x = FLT_MIN ;           // oh, since x was 0, i'll just make it really small instead.
float y = 0.f / x ;       // whew, `nan` didn't appear.

nanrisultati dall'operazione 0.f/0.f, o 0.0/0.0. nanè una terribile nemesi della stabilità del codice che deve essere rilevata e prevenuta con molta attenzione 1 . Le proprietà di nanciò sono diverse dai numeri normali:

  • nanè tossico, (5 * nan= nan)
  • nannon è uguale a niente, nemmeno a se stesso ( nan! = nan)
  • nannon più grande di niente ( nan!> 0)
  • nannon è meno di niente ( nan! <0)

Le ultime 2 proprietà elencate sono contro-logiche e comporteranno un comportamento dispari del codice che si basa sui confronti con un nannumero (anche la terza ultima proprietà è dispari, ma probabilmente non vedrai mai x != x ?nel tuo codice (a meno che tu non stia verificando per nan (inarrestabilmente))).

Nel mio codice, ho notato che i nanvalori tendono a produrre bug difficili da trovare. (Notare come questo non sia il caso di info -inf. ( -inf<0) restituisce TRUE, (0 < inf) restituisce VERO e persino ( -inf< inf) restituisce VERO. Pertanto, nella mia esperienza, il comportamento del codice è spesso ancora come desiderato).

cosa fare sotto nan

Quello che vuoi che succeda 0.0/0.0 deve essere trattato come un caso speciale , ma ciò che fai deve dipendere dai numeri che prevedi vengano emessi dal codice.

Nell'esempio sopra, il risultato di ( 0.f/FLT_MIN) sarà 0, sostanzialmente. Potresti voler 0.0/0.0generare HUGEinvece. Così,

float x=0.f, y=0.f, z;
if( !x && !y )    // 0.f/0.f case
  z = FLT_MAX ;   // biggest float possible
else
  z = y/x ;       // regular division.

Quindi in quanto sopra, se x lo fosse 0.f, infne deriverebbe (che ha un comportamento abbastanza buono / non distruttivo come sopra menzionato in realtà).

Ricordare che la divisione intera per 0 provoca un'eccezione di runtime . Quindi devi sempre controllare la divisione dei numeri interi per 0. Solo perché 0.0/0.0valuta silenziosamente nannon significa che puoi essere pigro e non controllare 0.0/0.0prima che accada.

1 I controlli per nanvia a x != xvolte sono inaffidabili ( x != xeliminati da alcuni compilatori di ottimizzazione che infrangono la conformità IEEE, in particolare quando lo -ffast-mathswitch è abilitato).


Grazie per averlo sottolineato; una programmazione del genere aiuterebbe sicuramente il problema in quanto tale. Ma la prossima volta, cerca di non abusare troppo delle funzionalità di formattazione del testo. Cambiare le dimensioni dei caratteri, il peso e lo stile in questo modo rende davvero difficile la lettura.
Magnus,

4
Si noti che 0.0 / 0.0 non è l'unica operazione che potrebbe provocare un NaN. La radice quadrata di un numero negativo restituisce NaN. Il coseno di + infinito restituisce anche NaN. l'operazione acos (x) in cui x non è compreso nell'intervallo [0, pi] può anche comportare NaN. In poche parole, bisogna stare molto attenti a guardare anche a queste operazioni potenzialmente rischiose, non solo a 0,0 / 0,0.
Boris Dalstein,

Totalmente d'accordo con Boris. Nella mia esperienza, NaN praticamente proveniva sempre da qualcosa come sqrt (-1.302e-53), cioè i risultati di calcolo intermedi vicini allo zero venivano immessi in sqrt senza verificare la negatività.
Hans_meine,

1
"Prevenire le NaN" significa che devi entrare in tutte le operazioni aritmetiche di base, non solo nella divisione. Dovrai fare attenzione a ∞ / ∞, 0 * ∞, ∞% x, x% 0, ∞ - ∞, 0 ^ 0, ∞ ^ 0, tra molti altri. Essere "preventivi" con tali operazioni aritmetiche di base significa che potenzierai completamente le tue prestazioni (e probabilmente perderai altri casi a cui non hai pensato).
Steve Hollasch,

11

A partire da C ++ 14 ci sono diversi modi per testare se un numero in virgola mobile valueè un NaN.

Di questi modi, solo il controllo dei bit della rappresentazione del numero funziona in modo affidabile, come indicato nella mia risposta originale. In particolare, std::isnane il controllo spesso proposto v != v, non funzionano in modo affidabile e non devono essere utilizzati, per evitare che il codice smetta di funzionare correttamente quando qualcuno decide che è necessaria l'ottimizzazione in virgola mobile e chiede al compilatore di farlo. Questa situazione può cambiare, i compilatori possono diventare più conformi, ma per questo problema che non si è verificato nei 6 anni dalla risposta originale.

Per circa 6 anni la mia risposta originale è stata la soluzione selezionata per questa domanda, che era OK. Ma recentemente v != vè stata selezionata una risposta altamente votata che raccomanda il test inaffidabile . Da qui questa ulteriore risposta più aggiornata (ora abbiamo gli standard C ++ 11 e C ++ 14 e C ++ 17 all'orizzonte).


I modi principali per verificare la presenza di NaN-ness, a partire da C ++ 14, sono:

  • std::isnan(value) )
    è il modo di libreria standard previsto dal C ++ 11. isnanapparentemente è in conflitto con la macro Posix con lo stesso nome, ma in pratica non è un problema. Il problema principale è che quando viene richiesta l'ottimizzazione aritmetica in virgola mobile, con almeno un compilatore principale, vale a dire g ++, viene std::isnan restituito l' falseargomento NaN .

  • (fpclassify(value) == FP_NAN) )
    Soffre dello stesso problema che std::isnan, ad esempio, non è affidabile.

  • (value != value) )
    Consigliato in molte risposte SO. Soffre dello stesso problema che std::isnan, ad esempio, non è affidabile.

  • (value == Fp_info::quiet_NaN()) )
    Questo è un test che con un comportamento standard non dovrebbe rilevare NaN, ma che con il comportamento ottimizzato potrebbe forse rilevare NaN (a causa del codice ottimizzato che confronta direttamente le rappresentazioni a livello di bit), e forse combinato con un altro modo per coprire il comportamento non ottimizzato standard , è in grado di rilevare in modo affidabile NaN. Purtroppo si è rivelato non funzionare in modo affidabile.

  • (ilogb(value) == FP_ILOGBNAN) )
    Soffre dello stesso problema che std::isnan, ad esempio, non è affidabile.

  • isunordered(1.2345, value) )
    Soffre dello stesso problema che std::isnan, ad esempio, non è affidabile.

  • is_ieee754_nan( value ) )
    Questa non è una funzione standard. Sta verificando i bit secondo lo standard IEEE 754. È completamente affidabile ma il codice dipende in qualche modo dal sistema.


Nel seguente codice di test completo, "successo" è se un'espressione riporta nanosità del valore. Per la maggior parte delle espressioni questa misura di successo, l'obiettivo di rilevare NaN e solo NaN, corrisponde alla loro semantica standard. Per l' (value == Fp_info::quiet_NaN()) )espressione, tuttavia, il comportamento standard è che non funziona come un rivelatore NaN.

#include <cmath>        // std::isnan, std::fpclassify
#include <iostream>
#include <iomanip>      // std::setw
#include <limits>
#include <limits.h>     // CHAR_BIT
#include <sstream>
#include <stdint.h>     // uint64_t
using namespace std;

#define TEST( x, expr, expected ) \
    [&](){ \
        const auto value = x; \
        const bool result = expr; \
        ostringstream stream; \
        stream << boolalpha << #x " = " << x << ", (" #expr ") = " << result; \
        cout \
            << setw( 60 ) << stream.str() << "  " \
            << (result == expected? "Success" : "FAILED") \
            << endl; \
    }()

#define TEST_ALL_VARIABLES( expression ) \
    TEST( v, expression, true ); \
    TEST( u, expression, false ); \
    TEST( w, expression, false )

using Fp_info = numeric_limits<double>;

inline auto is_ieee754_nan( double const x )
    -> bool
{
    static constexpr bool   is_claimed_ieee754  = Fp_info::is_iec559;
    static constexpr int    n_bits_per_byte     = CHAR_BIT;
    using Byte = unsigned char;

    static_assert( is_claimed_ieee754, "!" );
    static_assert( n_bits_per_byte == 8, "!" );
    static_assert( sizeof( x ) == sizeof( uint64_t ), "!" );

    #ifdef _MSC_VER
        uint64_t const bits = reinterpret_cast<uint64_t const&>( x );
    #else
        Byte bytes[sizeof(x)];
        memcpy( bytes, &x, sizeof( x ) );
        uint64_t int_value;
        memcpy( &int_value, bytes, sizeof( x ) );
        uint64_t const& bits = int_value;
    #endif

    static constexpr uint64_t   sign_mask       = 0x8000000000000000;
    static constexpr uint64_t   exp_mask        = 0x7FF0000000000000;
    static constexpr uint64_t   mantissa_mask   = 0x000FFFFFFFFFFFFF;

    (void) sign_mask;
    return (bits & exp_mask) == exp_mask and (bits & mantissa_mask) != 0;
}

auto main()
    -> int
{
    double const v = Fp_info::quiet_NaN();
    double const u = 3.14;
    double const w = Fp_info::infinity();

    cout << boolalpha << left;
    cout << "Compiler claims IEEE 754 = " << Fp_info::is_iec559 << endl;
    cout << endl;;
    TEST_ALL_VARIABLES( std::isnan(value) );                    cout << endl;
    TEST_ALL_VARIABLES( (fpclassify(value) == FP_NAN) );        cout << endl;
    TEST_ALL_VARIABLES( (value != value) );                     cout << endl;
    TEST_ALL_VARIABLES( (value == Fp_info::quiet_NaN()) );      cout << endl;
    TEST_ALL_VARIABLES( (ilogb(value) == FP_ILOGBNAN) );        cout << endl;
    TEST_ALL_VARIABLES( isunordered(1.2345, value) );           cout << endl;
    TEST_ALL_VARIABLES( is_ieee754_nan( value ) );
}

Risultati con g ++ (nota ancora che il comportamento standard di (value == Fp_info::quiet_NaN())è che non funziona come un rivelatore NaN, qui è solo molto di interesse pratico):

[C: \ my \ forum \ so \ 282 (rileva NaN)]
> g ++ --version | trova "++"
g ++ (x86_64-win32-sjlj-rev1, costruito dal progetto MinGW-W64) 6.3.0

[C: \ my \ forum \ so \ 282 (rileva NaN)]
> g ++ foo.cpp && a
Il compilatore afferma IEEE 754 = true

v = nan, (std :: isnan (value)) = true Successo
u = 3.14, (std :: isnan (value)) = false Operazione riuscita
w = inf, (std :: isnan (value)) = false Operazione riuscita

v = nan, ((fpclassify (value) == 0x0100)) = true Esito positivo
u = 3.14, ((fpclassify (value) == 0x0100)) = false Operazione riuscita
w = inf, ((fpclassify (value) == 0x0100)) = false Operazione riuscita

v = nan, ((value! = value)) = true Successo
u = 3.14, ((value! = value)) = false Operazione riuscita
w = inf, ((value! = value)) = false Operazione riuscita

v = nan, ((value == Fp_info :: quiet_NaN ())) = false FAILED
u = 3.14, ((value == Fp_info :: quiet_NaN ())) = false Operazione riuscita
w = inf, ((value == Fp_info :: quiet_NaN ())) = false Operazione riuscita

v = nan, ((ilogb (value) == ((int) 0x80000000))) = true Successo
u = 3.14, ((ilogb (value) == ((int) 0x80000000))) = false Successo
w = inf, ((ilogb (value) == ((int) 0x80000000))) = false Successo

v = nan, (isunordered (1.2345, value)) = true Success
u = 3.14, (non ordinato (1.2345, valore)) = false Successo
w = inf, (isunordered (1.2345, value)) = false Operazione riuscita

v = nan, (is_ieee754_nan (value)) = true Successo
u = 3.14, (is_ieee754_nan (value)) = false Operazione riuscita
w = inf, (is_ieee754_nan (value)) = false Operazione riuscita

[C: \ my \ forum \ so \ 282 (rileva NaN)]
> g ++ foo.cpp -ffast-math && a
Il compilatore afferma IEEE 754 = true

v = nan, (std :: isnan (value)) = false FAILED
u = 3.14, (std :: isnan (value)) = false Operazione riuscita
w = inf, (std :: isnan (value)) = false Operazione riuscita

v = nan, ((fpclassify (value) == 0x0100)) = false NON RIUSCITO
u = 3.14, ((fpclassify (value) == 0x0100)) = false Operazione riuscita
w = inf, ((fpclassify (value) == 0x0100)) = false Operazione riuscita

v = nan, ((value! = value)) = false FAILED
u = 3.14, ((value! = value)) = false Operazione riuscita
w = inf, ((value! = value)) = false Operazione riuscita

v = nan, ((value == Fp_info :: quiet_NaN ())) = true Successo
u = 3.14, ((value == Fp_info :: quiet_NaN ())) = true FAILED
w = inf, ((value == Fp_info :: quiet_NaN ())) = true FAILED

v = nan, ((ilogb (value) == ((int) 0x80000000))) = true Successo
u = 3.14, ((ilogb (value) == ((int) 0x80000000))) = false Successo
w = inf, ((ilogb (value) == ((int) 0x80000000))) = false Successo

v = nan, (non ordinato (1.2345, valore)) = false NON RIUSCITO
u = 3.14, (non ordinato (1.2345, valore)) = false Successo
w = inf, (isunordered (1.2345, value)) = false Operazione riuscita

v = nan, (is_ieee754_nan (value)) = true Successo
u = 3.14, (is_ieee754_nan (value)) = false Operazione riuscita
w = inf, (is_ieee754_nan (value)) = false Operazione riuscita

[C: \ my \ forum \ so \ 282 (rileva NaN)]
> _

Risultati con Visual C ++:

[C: \ my \ forum \ so \ 282 (rileva NaN)]
> cl / nologo- 2> & 1 | trova "++"
Compilatore di ottimizzazione Microsoft (R) C / C ++ versione 19.00.23725 per x86

[C: \ my \ forum \ so \ 282 (rileva NaN)]
> cl foo.cpp / Feb && b
foo.cpp
Il compilatore afferma IEEE 754 = true

v = nan, (std :: isnan (value)) = true Successo
u = 3.14, (std :: isnan (value)) = false Operazione riuscita
w = inf, (std :: isnan (value)) = false Operazione riuscita

v = nan, ((fpclassify (value) == 2)) = true Successo
u = 3.14, ((fpclassify (value) == 2)) = false Operazione riuscita
w = inf, ((fpclassify (value) == 2)) = false Operazione riuscita

v = nan, ((value! = value)) = true Successo
u = 3.14, ((value! = value)) = false Operazione riuscita
w = inf, ((value! = value)) = false Operazione riuscita

v = nan, ((value == Fp_info :: quiet_NaN ())) = false FAILED
u = 3.14, ((value == Fp_info :: quiet_NaN ())) = false Operazione riuscita
w = inf, ((value == Fp_info :: quiet_NaN ())) = false Operazione riuscita

v = nan, ((ilogb (value) == 0x7fffffff)) = true Successo
u = 3.14, ((ilogb (value) == 0x7fffffff)) = false Successo
w = inf, ((ilogb (value) == 0x7fffffff)) = true FAILED

v = nan, (isunordered (1.2345, value)) = true Success
u = 3.14, (non ordinato (1.2345, valore)) = false Successo
w = inf, (isunordered (1.2345, value)) = false Operazione riuscita

v = nan, (is_ieee754_nan (value)) = true Successo
u = 3.14, (is_ieee754_nan (value)) = false Operazione riuscita
w = inf, (is_ieee754_nan (value)) = false Operazione riuscita

[C: \ my \ forum \ so \ 282 (rileva NaN)]
> cl foo.cpp / feb / fp: veloce && b
foo.cpp
Il compilatore afferma IEEE 754 = true

v = nan, (std :: isnan (value)) = true Successo
u = 3.14, (std :: isnan (value)) = false Operazione riuscita
w = inf, (std :: isnan (value)) = false Operazione riuscita

v = nan, ((fpclassify (value) == 2)) = true Successo
u = 3.14, ((fpclassify (value) == 2)) = false Operazione riuscita
w = inf, ((fpclassify (value) == 2)) = false Operazione riuscita

v = nan, ((value! = value)) = true Successo
u = 3.14, ((value! = value)) = false Operazione riuscita
w = inf, ((value! = value)) = false Operazione riuscita

v = nan, ((value == Fp_info :: quiet_NaN ())) = false FAILED
u = 3.14, ((value == Fp_info :: quiet_NaN ())) = false Operazione riuscita
w = inf, ((value == Fp_info :: quiet_NaN ())) = false Operazione riuscita

v = nan, ((ilogb (value) == 0x7fffffff)) = true Successo
u = 3.14, ((ilogb (value) == 0x7fffffff)) = false Successo
w = inf, ((ilogb (value) == 0x7fffffff)) = true FAILED

v = nan, (isunordered (1.2345, value)) = true Success
u = 3.14, (non ordinato (1.2345, valore)) = false Successo
w = inf, (isunordered (1.2345, value)) = false Operazione riuscita

v = nan, (is_ieee754_nan (value)) = true Successo
u = 3.14, (is_ieee754_nan (value)) = false Operazione riuscita
w = inf, (is_ieee754_nan (value)) = false Operazione riuscita

[C: \ my \ forum \ so \ 282 (rileva NaN)]
> _

Riassumendo i risultati di cui sopra, solo il test diretto della rappresentazione a livello di bit, utilizzando la is_ieee754_nanfunzione definita in questo programma di test, ha funzionato in modo affidabile in tutti i casi sia con g ++ che con Visual C ++.


Addendum:
Dopo aver pubblicato quanto sopra sono venuto a conoscenza di un altro possibile test per NaN, menzionato in un'altra risposta qui, vale a dire ((value < 0) == (value >= 0)). Ciò si è rivelato funzionare bene con Visual C ++ ma non è riuscito con l' -ffast-mathopzione di g ++ . Solo i test bitmap diretti funzionano in modo affidabile.


7
inline bool IsNan(float f)
{
    const uint32 u = *(uint32*)&f;
    return (u&0x7F800000) == 0x7F800000 && (u&0x7FFFFF);    // Both NaN and qNan.
}

inline bool IsNan(double d)
{
    const uint64 u = *(uint64*)&d;
    return (u&0x7FF0000000000000ULL) == 0x7FF0000000000000ULL && (u&0xFFFFFFFFFFFFFULL);
}

Funziona se sizeof(int)è 4 ed sizeof(long long)è 8.

Durante il tempo di esecuzione è solo un confronto, i casting non richiedono tempo. Cambia solo la configurazione dei flag di confronto per verificare l'uguaglianza.


Inoltre, è limitato alla rappresentazione IEEE 754.
Saluti e hth. - Alf

Nota che questo cast infrange la rigida regola di aliasing di g ++ e che il compilatore è noto per fare Unmentionable Things ™ quando rileva un UB formale. Invece di cast efficienti, con g ++ devi usare memcpy, per sicurezza, tramite un array di byte. Codice per quello nella mia risposta n . 2 .
Saluti e hth. - Alf,

4

Una possibile soluzione che non dipenderebbe dalla specifica rappresentazione IEEE per NaN utilizzata sarebbe la seguente:

template<class T>
bool isnan( T f ) {
    T _nan =  (T)0.0/(T)0.0;
    return 0 == memcmp( (void*)&f, (void*)&_nan, sizeof(T) );
}

Il virgola mobile a precisione singola ha oltre 8 milioni di rappresentazioni di bit legittime e diverse per NaN, quindi è necessario aggiungere altri confronti. :)
Steve Hollasch,

4

Considerando che (x! = X) non è sempre garantito per NaN (come se usassi l'opzione -ffast-math), ho usato:

#define IS_NAN(x) (((x) < 0) == ((x) >= 0))

I numeri non possono essere sia <0 che> = 0, quindi in realtà questo controllo passa solo se il numero non è né minore di, né maggiore o uguale a zero. Che in pratica non è un numero o NaN.

Puoi anche usarlo se preferisci:

#define IS_NAN(x) (!((x)<0) && !((x)>=0)

Non sono sicuro di come questo sia influenzato da -ffast-math, quindi il tuo chilometraggio può variare.


Questo è in realtà difettoso allo stesso modo f != fè difettoso. Ho visto llvm ottimizzare un pezzo di codice quasi identico. L'ottimizzatore può propagare le informazioni sul primo confronto e capire che il secondo confronto potrebbe non essere vero se il primo lo è. (se il compilatore obbedisce rigorosamente alle regole IEEE f != fè comunque molto più semplice)
Markus

Non funziona con l' -ffast-mathopzione di g ++ . Funziona con Visual C ++. Vedi ( stackoverflow.com/a/42138465/464581 ).
Saluti e hth. - Alf

3

Per quanto mi riguarda, la soluzione potrebbe essere una macro per renderla esplicitamente in linea e quindi abbastanza veloce. Funziona anche con qualsiasi tipo di galleggiante. Si basa sul fatto che l'unico caso in cui un valore non è uguale a se stesso è quando il valore non è un numero.

#ifndef isnan
  #define isnan(a) (a != a)
#endif

Questa è una delle migliori risposte a questa domanda! Grazie per aver condiviso.
Henri Menke,

2
Altre risposte indicano che ciò può fallire con l'opzione -ffast-math impostata.
Tecnophile,

3

Questo funziona:

#include <iostream>
#include <math.h>
using namespace std;

int main ()
{
  char ch='a';
  double val = nan(&ch);
  if(isnan(val))
     cout << "isnan" << endl;

  return 0;
}

uscita: isnan


1

Mi sembra che il miglior approccio realmente multipiattaforma sarebbe quello di utilizzare un sindacato e testare il modello di bit del doppio per verificare la presenza di NaN.

Non ho testato a fondo questa soluzione e potrebbe esserci un modo più efficiente di lavorare con i modelli di bit, ma penso che dovrebbe funzionare.

#include <stdint.h>
#include <stdio.h>

union NaN
{
    uint64_t bits;
    double num;
};

int main()
{
    //Test if a double is NaN
    double d = 0.0 / 0.0;
    union NaN n;
    n.num = d;
    if((n.bits | 0x800FFFFFFFFFFFFF) == 0xFFFFFFFFFFFFFFFF)
    {
        printf("NaN: %f", d);
    }

    return 0;
}

Si noti che "è un comportamento indefinito leggere dal membro del sindacato che non è stato scritto più di recente". Quindi questo uso di un uniontipo di gioco di parole tra due tipi potrebbe non funzionare come desiderato (: sad_panda :). Il modo corretto (anche se in realtà non portatile come desiderato) sarebbe quello di evitare del tutto l'unione e fare un memcpy da doublein una uint64_tvariabile diversa , quindi fare il test usando quella variabile helper.
Eljay,

0

Su x86-64 puoi avere metodi estremamente veloci per controllare NaN e infinito, che funzionano indipendentemente -ffast-mathdall'opzione del compilatore. ( f != f, std::isnan, std::isinfResa sempre falsecon -ffast-math).


I test per i numeri NaN, infinito e finiti possono essere facilmente eseguiti controllando il massimo esponente. l'infinito è l'esponente massimo con mantissa zero, NaN è l'esponente massimo e mantissa diversa da zero. L'esponente viene memorizzato nei bit successivi dopo il bit del segno più in alto, in modo che possiamo semplicemente lasciare shift per sbarazzarci del bit del segno e rendere l'esponente i bit più in alto, non operator&è necessario mascherare ( ):

static inline uint64_t load_ieee754_rep(double a) {
    uint64_t r;
    static_assert(sizeof r == sizeof a, "Unexpected sizes.");
    std::memcpy(&r, &a, sizeof a); // Generates movq instruction.
    return r;
}

static inline uint32_t load_ieee754_rep(float a) {
    uint32_t r;
    static_assert(sizeof r == sizeof a, "Unexpected sizes.");
    std::memcpy(&r, &a, sizeof a); // Generates movd instruction.
    return r;
}

constexpr uint64_t inf_double_shl1 = UINT64_C(0xffe0000000000000);
constexpr uint32_t inf_float_shl1 = UINT32_C(0xff000000);

// The shift left removes the sign bit. The exponent moves into the topmost bits,
// so that plain unsigned comparison is enough.
static inline bool isnan2(double a)    { return load_ieee754_rep(a) << 1  > inf_double_shl1; }
static inline bool isinf2(double a)    { return load_ieee754_rep(a) << 1 == inf_double_shl1; }
static inline bool isfinite2(double a) { return load_ieee754_rep(a) << 1  < inf_double_shl1; }
static inline bool isnan2(float a)     { return load_ieee754_rep(a) << 1  > inf_float_shl1; }
static inline bool isinf2(float a)     { return load_ieee754_rep(a) << 1 == inf_float_shl1; }
static inline bool isfinite2(float a)  { return load_ieee754_rep(a) << 1  < inf_float_shl1; }

Le stdversioni isinfe isfinitecaricano 2 double/floatcostanti dal .datasegmento e, nel peggiore dei casi, possono causare 2 errori nella cache dei dati. Le versioni precedenti non caricano alcun dato inf_double_shl1e le inf_float_shl1costanti vengono codificate come operandi immediati nelle istruzioni di assemblaggio.


Più veloce isnan2è solo 2 istruzioni di montaggio:

bool isnan2(double a) {
    bool r;
    asm(".intel_syntax noprefix"
        "\n\t ucomisd %1, %1"
        "\n\t setp %b0"
        "\n\t .att_syntax prefix"
        : "=g" (r)
        : "x" (a)
        : "cc"
        );
    return r;
}

Utilizza il fatto che l' ucomisdistruzione imposta il flag di parità se un argomento è NaN. std::isnanFunziona così quando non -ffast-mathviene specificata alcuna opzione.


-1

Lo standard IEEE dice che quando l'esponente è tutto se 1la mantissa non è zero, il numero è a NaN. Il doppio è 1bit di segno, 11bit di esponente e 52bit di mantissa. Fai un po 'di controllo.


-3

Come i commenti sopra affermano che a! = A non funzionerà in g ++ e in alcuni altri compilatori, ma questo trucco dovrebbe. Potrebbe non essere così efficiente, ma è ancora un modo:

bool IsNan(float a)
{
    char s[4];
    sprintf(s, "%.3f", a);
    if (s[0]=='n') return true;
    else return false;
}

Fondamentalmente, in g ++ (non sono sicuro degli altri però) printf stampa 'nan' nei formati% d o% .f se la variabile non è un numero intero / float valido. Pertanto, questo codice sta verificando che il primo carattere della stringa sia 'n' (come in "nan")


2
Ciò non causerebbe un overflow del buffer se a = 234324.0f?
Mazyod,

Sì, o 340282346638528859811704183484516925440.000se a = FLT_MAX. char s[7]; sprintf(s, "%.0g", a);a=-FLT_MAX-3e+38
Dovrebbe

-3

Questo rileva l'infinito e anche NaN in Visual Studio controllando che sia all'interno di doppi limiti:

//#include <float.h>
double x, y = -1.1; x = sqrt(y);
if (x >= DBL_MIN && x <= DBL_MAX )
    cout << "DETECTOR-2 of errors FAILS" << endl;
else
    cout << "DETECTOR-2 of errors OK" << endl;

Controlla la definizione di FLT_MIN, DBL_MINe LDBL_MINpiù attentamente. Questi sono definiti come i valori normalizzati più piccoli per ciascun tipo. Ad esempio, la precisione singola ha oltre 8 milioni di valori denorm legittimi che sono maggiori di zero e minori di FLT_MIN(e non sono NaN).
Steve Hollasch,
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.