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::isnan
e 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. isnan
apparentemente è 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' false
argomento 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_nan
funzione 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-math
opzione di g ++ . Solo i test bitmap diretti funzionano in modo affidabile.