Począwszy od C ++ 14 istnieje wiele sposobów sprawdzenia, czy liczba zmiennoprzecinkowa value
jest NaN.
Spośród tych sposobów tylko sprawdzanie bitów reprezentacji liczby działa niezawodnie, jak zauważono w mojej pierwotnej odpowiedzi. W szczególności std::isnan
i często proponowana kontrola v != v
nie działa niezawodnie i nie powinna być używana, aby Twój kod nie przestał działać poprawnie, gdy ktoś zdecyduje, że potrzebna jest optymalizacja zmiennoprzecinkowa, i poprosi kompilator o zrobienie tego. Ta sytuacja może się zmienić, kompilatory mogą uzyskać większą zgodność, ale w przypadku tego problemu, który nie pojawił się przez 6 lat od pierwotnej odpowiedzi.
Przez około 6 lat moją pierwotną odpowiedzią było wybrane rozwiązanie tego pytania, które było OK. Ale ostatnio wybrano bardzo pozytywną odpowiedź zalecającą niewiarygodny v != v
test. Stąd ta dodatkowa, bardziej aktualna odpowiedź (teraz mamy standardy C ++ 11 i C ++ 14 oraz C ++ 17 na horyzoncie).
Główne sposoby sprawdzania obecności NaN od C ++ 14 to:
std::isnan(value) )
jest zamierzonym standardowym sposobem biblioteki od C ++ 11. isnan
najwyraźniej jest w konflikcie z makrem Posix o tej samej nazwie, ale w praktyce nie stanowi to problemu. Główny problem polega na tym, że gdy wymagana jest optymalizacja arytmetyczna zmiennoprzecinkowa, wówczas co najmniej jeden główny kompilator, mianowicie g ++, std::isnan
zwraca false
argument NaN .
(fpclassify(value) == FP_NAN) )
Cierpi na ten sam problem, co std::isnan
np. Nie jest wiarygodny.
(value != value) )
Zalecane w wielu odpowiedziach SO. Cierpi na ten sam problem, co std::isnan
np. Nie jest wiarygodny.
(value == Fp_info::quiet_NaN()) )
Jest to test, który przy standardowym zachowaniu nie powinien wykrywać NaN, ale że przy zoptymalizowanym zachowaniu może wykryć NaN (ze względu na zoptymalizowany kod bezpośrednio porównujący reprezentacje poziomu bitów) i być może w połączeniu z innym sposobem na pokrycie standardowego niezoptymalizowanego zachowania , mógł niezawodnie wykryć NaN. Niestety okazało się, że nie działa niezawodnie.
(ilogb(value) == FP_ILOGBNAN) )
Cierpi na ten sam problem, co std::isnan
np. Nie jest wiarygodny.
isunordered(1.2345, value) )
Cierpi na ten sam problem, co std::isnan
np. Nie jest wiarygodny.
is_ieee754_nan( value ) )
To nie jest standardowa funkcja. Sprawdza bity zgodnie ze standardem IEEE 754. Jest całkowicie niezawodny, ale kod jest w pewnym stopniu zależny od systemu.
W poniższym kompletnym kodzie testowym „sukces” określa, czy wyrażenie zgłasza Nanowość wartości. Dla większości wyrażeń ta miara sukcesu, cel wykrycia NaN i tylko NaN, odpowiada ich standardowej semantyce. Jednak w przypadku (value == Fp_info::quiet_NaN()) )
wyrażenia standardowe zachowanie polega na tym, że nie działa ono jako detektor 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 ) );
}
Wyniki z g ++ (zauważ, że standardowe zachowanie (value == Fp_info::quiet_NaN())
jest takie, że nie działa jako detektor NaN, jest to po prostu bardzo praktyczne zainteresowanie tutaj):
[C: \ my \ forums \ so \ 282 (wykryj NaN)]
> g ++ --wersja | znajdź „++”
g ++ (x86_64-win32-sjlj-rev1, zbudowany przez projekt MinGW-W64) 6.3.0
[C: \ my \ forums \ so \ 282 (wykryj NaN)]
> g ++ foo.cpp && a
Kompilator twierdzi, że IEEE 754 = true
v = nan, (std :: isnan (wartość)) = true Sukces
u = 3,14, (std :: isnan (wartość)) = false Sukces
w = inf, (std :: isnan (wartość)) = false Sukces
v = nan, ((fpclassify (wartość) == 0x0100)) = true Sukces
u = 3,14, ((fpclassify (wartość) == 0x0100)) = fałszywy sukces
w = inf, ((fpclassify (wartość) == 0x0100)) = false Sukces
v = nan, ((wartość! = wartość)) = true Sukces
u = 3,14, ((wartość! = wartość)) = false Sukces
w = inf, ((wartość! = wartość)) = false Sukces
v = nan, ((wartość == Fp_info :: quiet_NaN ())) = false FAILED
u = 3,14, ((wartość == Fp_info :: quiet_NaN ())) = false Sukces
w = inf, ((wartość == Fp_info :: quiet_NaN ())) = false Sukces
v = nan, ((ilogb (wartość) == ((int) 0x80000000))) = true Sukces
u = 3,14, ((ilogb (wartość) == ((int) 0x80000000))) = false Sukces
w = inf, ((ilogb (wartość) == ((int) 0x80000000))) = false Sukces
v = nan, (isunordered (1.2345, wartość)) = true Sukces
u = 3,14, (isunordered (1,2345, wartość)) = false Sukces
w = inf, (isunordered (1.2345, wartość)) = false Sukces
v = nan, (is_ieee754_nan (wartość)) = true Sukces
u = 3,14, (is_ieee754_nan (wartość)) = false Sukces
w = inf, (is_ieee754_nan (wartość)) = false Sukces
[C: \ my \ forums \ so \ 282 (wykryj NaN)]
> g ++ foo.cpp -ffast-math && a
Kompilator twierdzi, że IEEE 754 = true
v = nan, (std :: isnan (wartość)) = false FAILED
u = 3,14, (std :: isnan (wartość)) = false Sukces
w = inf, (std :: isnan (wartość)) = false Sukces
v = nan, ((fpclifyify (wartość) == 0x0100)) = false FAILED
u = 3,14, ((fpclassify (wartość) == 0x0100)) = fałszywy sukces
w = inf, ((fpclassify (wartość) == 0x0100)) = false Sukces
v = nan, ((wartość! = wartość)) = false FAILED
u = 3,14, ((wartość! = wartość)) = false Sukces
w = inf, ((wartość! = wartość)) = false Sukces
v = nan, ((wartość == Fp_info :: quiet_NaN ())) = true Sukces
u = 3,14, ((wartość == Fp_info :: quiet_NaN ())) = true FAILED
w = inf, ((wartość == Fp_info :: quiet_NaN ())) = true FAILED
v = nan, ((ilogb (wartość) == ((int) 0x80000000))) = true Sukces
u = 3,14, ((ilogb (wartość) == ((int) 0x80000000))) = false Sukces
w = inf, ((ilogb (wartość) == ((int) 0x80000000))) = false Sukces
v = nan, (isunordered (1.2345, wartość)) = false FAILED
u = 3,14, (isunordered (1,2345, wartość)) = false Sukces
w = inf, (isunordered (1.2345, wartość)) = false Sukces
v = nan, (is_ieee754_nan (wartość)) = true Sukces
u = 3,14, (is_ieee754_nan (wartość)) = false Sukces
w = inf, (is_ieee754_nan (wartość)) = false Sukces
[C: \ my \ forums \ so \ 282 (wykryj NaN)]
> _
Wyniki z Visual C ++:
[C: \ my \ forums \ so \ 282 (wykryj NaN)]
> cl / nologo- 2> i 1 | znajdź „++”
Microsoft (R) C / C ++ Optimization Compiler Version 19.00.23725 dla x86
[C: \ my \ forums \ so \ 282 (wykryj NaN)]
> cl foo.cpp / Feb && b
foo.cpp
Kompilator twierdzi, że IEEE 754 = true
v = nan, (std :: isnan (wartość)) = true Sukces
u = 3,14, (std :: isnan (wartość)) = false Sukces
w = inf, (std :: isnan (wartość)) = false Sukces
v = nan, ((fpclassify (wartość) == 2)) = true Sukces
u = 3,14, ((fpclassify (wartość) == 2)) = fałszywy sukces
w = inf, ((fpclassify (wartość) == 2)) = false Sukces
v = nan, ((wartość! = wartość)) = true Sukces
u = 3,14, ((wartość! = wartość)) = false Sukces
w = inf, ((wartość! = wartość)) = false Sukces
v = nan, ((wartość == Fp_info :: quiet_NaN ())) = false FAILED
u = 3,14, ((wartość == Fp_info :: quiet_NaN ())) = false Sukces
w = inf, ((wartość == Fp_info :: quiet_NaN ())) = false Sukces
v = nan, ((ilogb (wartość) == 0x7fffffff)) = true Sukces
u = 3,14, ((ilogb (wartość) == 0x7fffffff)) = fałszywy sukces
w = inf, ((ilogb (wartość) == 0x7fffffff)) = true FAILED
v = nan, (isunordered (1.2345, wartość)) = true Sukces
u = 3,14, (isunordered (1,2345, wartość)) = false Sukces
w = inf, (isunordered (1.2345, wartość)) = false Sukces
v = nan, (is_ieee754_nan (wartość)) = true Sukces
u = 3,14, (is_ieee754_nan (wartość)) = false Sukces
w = inf, (is_ieee754_nan (wartość)) = false Sukces
[C: \ my \ forums \ so \ 282 (wykryj NaN)]
> cl foo.cpp / Feb / fp: fast && b
foo.cpp
Kompilator twierdzi, że IEEE 754 = true
v = nan, (std :: isnan (wartość)) = true Sukces
u = 3,14, (std :: isnan (wartość)) = false Sukces
w = inf, (std :: isnan (wartość)) = false Sukces
v = nan, ((fpclassify (wartość) == 2)) = true Sukces
u = 3,14, ((fpclassify (wartość) == 2)) = fałszywy sukces
w = inf, ((fpclassify (wartość) == 2)) = false Sukces
v = nan, ((wartość! = wartość)) = true Sukces
u = 3,14, ((wartość! = wartość)) = false Sukces
w = inf, ((wartość! = wartość)) = false Sukces
v = nan, ((wartość == Fp_info :: quiet_NaN ())) = false FAILED
u = 3,14, ((wartość == Fp_info :: quiet_NaN ())) = false Sukces
w = inf, ((wartość == Fp_info :: quiet_NaN ())) = false Sukces
v = nan, ((ilogb (wartość) == 0x7fffffff)) = true Sukces
u = 3,14, ((ilogb (wartość) == 0x7fffffff)) = fałszywy sukces
w = inf, ((ilogb (wartość) == 0x7fffffff)) = true FAILED
v = nan, (isunordered (1.2345, wartość)) = true Sukces
u = 3,14, (isunordered (1,2345, wartość)) = false Sukces
w = inf, (isunordered (1.2345, wartość)) = false Sukces
v = nan, (is_ieee754_nan (wartość)) = true Sukces
u = 3,14, (is_ieee754_nan (wartość)) = false Sukces
w = inf, (is_ieee754_nan (wartość)) = false Sukces
[C: \ my \ forums \ so \ 282 (wykryj NaN)]
> _
Podsumowując powyższe wyniki, tylko bezpośrednie testowanie reprezentacji na poziomie bitów przy użyciu is_ieee754_nan
funkcji zdefiniowanej w tym programie testowym działało niezawodnie we wszystkich przypadkach zarówno z g ++, jak i Visual C ++.
Dodatek:
Po opublikowaniu powyższego dowiedziałem się o jeszcze jednym możliwym teście na NaN, wymienionym w innej odpowiedzi tutaj, mianowicie ((value < 0) == (value >= 0))
. Okazało się, że działa dobrze z Visual C ++, ale zawiodło z -ffast-math
opcją g ++ . Tylko bezpośrednie testowanie bitatternów działa niezawodnie.