Станом на C ++ 14 існує декілька способів перевірити, чи є число плаваючої точки value
NaN.
З цих способів надійно працює лише перевірка бітів представлення числа, як зазначено в моїй оригінальній відповіді. Зокрема, std::isnan
і часто пропонована перевірка v != v
не працює надійно і не повинна використовуватися, щоб ваш код не перестав працювати правильно, коли хтось вирішить, що потрібна оптимізація з плаваючою комою, і попросить компілятор зробити це. Ця ситуація може змінитись, компілятори можуть отримати більше відповідності, але щодо цього питання, що не трапилося за 6 років з часу первинної відповіді.
Близько 6 років моєю оригінальною відповіддю було вибране рішення цього питання, яке було нормально. Але нещодавно було обрано висококваліфіковану відповідь, що рекомендує ненадійний v != v
тест. Звідси ця додаткова більш сучасна відповідь (тепер у нас є стандарти C ++ 11 і C ++ 14, а на горизонті C ++ 17).
Основними способами перевірити наявність NaN-ness, як для C ++ 14, є:
std::isnan(value) )
- це призначений стандартний спосіб бібліотеки, оскільки C ++ 11. isnan
мабуть, суперечить однойменному макросу Posix, але на практиці це не проблема. Основна проблема полягає в тому, що коли запитується арифметична оптимізація з плаваючою комою, то принаймні один основний компілятор, а саме g ++, std::isnan
повертається false
для аргументу NaN .
(fpclassify(value) == FP_NAN) )
Страждає від тієї ж проблеми, що і std::isnan
, тобто, не є надійною.
(value != value) )
Рекомендується у багатьох відповідях на відповідь. Страждає від тієї ж проблеми, що і std::isnan
, тобто, не є надійною.
(value == Fp_info::quiet_NaN()) )
Це тест, який зі стандартною поведінкою не повинен виявляти NaN, але з оптимізованою поведінкою, можливо, міг би виявити NaN (завдяки оптимізованому коду просто порівнюючи представлення бітрівневих рівнів), і, можливо, поєднується з іншим способом покриття стандартної неоптимізованої поведінки , може надійно виявити NaN. На жаль, виявилося, що він не працює надійно.
(ilogb(value) == FP_ILOGBNAN) )
Страждає від тієї ж проблеми, що і std::isnan
, тобто, не є надійною.
isunordered(1.2345, value) )
Страждає від тієї ж проблеми, що і std::isnan
, тобто, не є надійною.
is_ieee754_nan( value ) )
Це не стандартна функція. Це перевірка бітів відповідно до стандарту IEEE 754. Це повністю надійно, але код дещо залежить від системи.
У поданому нижче повному тестовому коді «успіх» - це те, чи вираження повідомляє про значення значення. Для більшості виразів цей показник успіху, мета виявлення NaN та лише NaN, відповідає їхній стандартній семантиці. Однак для (value == Fp_info::quiet_NaN()) )
виразу стандартна поведінка полягає в тому, що він не працює як 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 ) );
}
Результати з g ++ (зауважте ще раз, що стандартна поведінка (value == Fp_info::quiet_NaN())
полягає в тому, що він не працює як NaN-детектор, тут просто великий інтерес):
[C: \ my \ forums \ so \ 282 (виявити NaN)]
> g ++ --версія | знайти "++"
g ++ (x86_64-win32-sjlj-rev1, побудований проектом MinGW-W64) 6.3.0
[C: \ my \ forums \ so \ 282 (виявити NaN)]
> g ++ foo.cpp && a
Компілятор стверджує, що IEEE 754 = вірно
v = nan, (std :: isnan (значення)) = справжній Успіх
u = 3,14, (std :: isnan (значення)) = помилковий Успіх
w = inf, (std :: isnan (значення)) = хибний Успіх
v = nan, ((fpclassify (значення) == 0x0100)) = справжній Успіх
u = 3,14, ((fpclassify (значення) == 0x0100)) = хибний Успіх
w = inf, ((fpclassify (значення) == 0x0100)) = помилковий успіх
v = nan, ((value! = value)) = true Успіх
u = 3,14, ((значення! = значення)) = помилковий Успіх
w = inf, ((value! = value)) = хибний Успіх
v = nan, ((значення == Fp_info :: silent_NaN ())) = false FAILED
u = 3.14, ((значення == Fp_info :: silent_NaN ())) = помилковий Успіх
w = inf, ((значення == Fp_info :: silent_NaN ())) = помилковий Успіх
v = nan, ((ilogb (значення) == ((int) 0x80000000))) = справжній Успіх
u = 3.14, ((ilogb (значення) == ((int) 0x80000000))) = помилковий успіх
w = inf, ((ilogb (значення) == ((int) 0x80000000))) = хибний Успіх
v = nan, (не упорядковане (1,2345, значення)) = справжній Успіх
u = 3,14, (невпорядковано (1,2345, значення)) = хибний Успіх
w = inf, (невпорядкований (1,2345, значення)) = хибний Успіх
v = nan, (is_ieee754_nan (значення)) = справжній Успіх
u = 3,14, (is_ieee754_nan (значення)) = помилковий Успіх
w = inf, (is_ieee754_nan (значення)) = помилковий Успіх
[C: \ my \ forums \ so \ 282 (виявити NaN)]
> g ++ foo.cpp -фаст-математика && a
Компілятор стверджує, що IEEE 754 = вірно
v = nan, (std :: isnan (значення)) = false FAILED
u = 3,14, (std :: isnan (значення)) = помилковий Успіх
w = inf, (std :: isnan (значення)) = хибний Успіх
v = nan, ((fpclassify (значення) == 0x0100)) = false FAILED
u = 3,14, ((fpclassify (значення) == 0x0100)) = хибний Успіх
w = inf, ((fpclassify (значення) == 0x0100)) = помилковий успіх
v = nan, ((value! = value)) = false FAILED
u = 3,14, ((значення! = значення)) = помилковий Успіх
w = inf, ((value! = value)) = хибний Успіх
v = nan, ((значення == Fp_info :: silent_NaN ())) = справжній Успіх
u = 3,14, ((значення == Fp_info :: тихий_NaN ())) = справжній НЕПРАВНИЙ
w = inf, ((значення == Fp_info :: silent_NaN ())) = true FAILED
v = nan, ((ilogb (значення) == ((int) 0x80000000))) = справжній Успіх
u = 3.14, ((ilogb (значення) == ((int) 0x80000000))) = помилковий успіх
w = inf, ((ilogb (значення) == ((int) 0x80000000))) = хибний Успіх
v = nan, (невпорядковане (1,2345, значення)) = помилково НЕПРАВНО
u = 3,14, (невпорядковано (1,2345, значення)) = хибний Успіх
w = inf, (невпорядкований (1,2345, значення)) = хибний Успіх
v = nan, (is_ieee754_nan (значення)) = справжній Успіх
u = 3,14, (is_ieee754_nan (значення)) = помилковий Успіх
w = inf, (is_ieee754_nan (значення)) = помилковий Успіх
[C: \ my \ forums \ so \ 282 (виявити NaN)]
> _
Результати з Visual C ++:
[C: \ my \ forums \ so \ 282 (виявити NaN)]
> кл / нолого- 2> & 1 | знайти "++"
Microsoft (R) C / C ++ Оптимізація компілятора версія 19.00.23725 для x86
[C: \ my \ forums \ so \ 282 (виявити NaN)]
> cl foo.cpp / лютий && b
foo.cpp
Компілятор стверджує, що IEEE 754 = вірно
v = nan, (std :: isnan (значення)) = справжній Успіх
u = 3,14, (std :: isnan (значення)) = помилковий Успіх
w = inf, (std :: isnan (значення)) = хибний Успіх
v = nan, ((fpclassify (значення) == 2)) = справжній Успіх
u = 3.14, ((fpclassify (значення) == 2)) = хибний Успіх
w = inf, ((fpclassify (значення) == 2)) = хибний Успіх
v = nan, ((value! = value)) = true Успіх
u = 3,14, ((значення! = значення)) = помилковий Успіх
w = inf, ((value! = value)) = хибний Успіх
v = nan, ((значення == Fp_info :: silent_NaN ())) = false FAILED
u = 3.14, ((значення == Fp_info :: silent_NaN ())) = помилковий Успіх
w = inf, ((значення == Fp_info :: silent_NaN ())) = помилковий Успіх
v = nan, ((ilogb (значення) == 0x7fffffff)) = справжній успіх
u = 3.14, ((ilogb (значення) == 0x7fffffff)) = помилковий успіх
w = inf, ((ilogb (значення) == 0x7fffffff)) = справжній НЕПРАВНИЙ
v = nan, (не упорядковане (1,2345, значення)) = справжній Успіх
u = 3,14, (невпорядковано (1,2345, значення)) = хибний Успіх
w = inf, (невпорядкований (1,2345, значення)) = хибний Успіх
v = nan, (is_ieee754_nan (значення)) = справжній Успіх
u = 3,14, (is_ieee754_nan (значення)) = помилковий Успіх
w = inf, (is_ieee754_nan (значення)) = помилковий Успіх
[C: \ my \ forums \ so \ 282 (виявити NaN)]
> cl foo.cpp / лют / fp: швидко && b
foo.cpp
Компілятор стверджує, що IEEE 754 = вірно
v = nan, (std :: isnan (значення)) = справжній Успіх
u = 3,14, (std :: isnan (значення)) = помилковий Успіх
w = inf, (std :: isnan (значення)) = хибний Успіх
v = nan, ((fpclassify (значення) == 2)) = справжній Успіх
u = 3.14, ((fpclassify (значення) == 2)) = хибний Успіх
w = inf, ((fpclassify (значення) == 2)) = хибний Успіх
v = nan, ((value! = value)) = true Успіх
u = 3,14, ((значення! = значення)) = помилковий Успіх
w = inf, ((value! = value)) = хибний Успіх
v = nan, ((значення == Fp_info :: silent_NaN ())) = false FAILED
u = 3.14, ((значення == Fp_info :: silent_NaN ())) = помилковий Успіх
w = inf, ((значення == Fp_info :: silent_NaN ())) = помилковий Успіх
v = nan, ((ilogb (значення) == 0x7fffffff)) = справжній успіх
u = 3.14, ((ilogb (значення) == 0x7fffffff)) = помилковий успіх
w = inf, ((ilogb (значення) == 0x7fffffff)) = справжній НЕПРАВНИЙ
v = nan, (не упорядковане (1,2345, значення)) = справжній Успіх
u = 3,14, (невпорядковано (1,2345, значення)) = хибний Успіх
w = inf, (невпорядкований (1,2345, значення)) = хибний Успіх
v = nan, (is_ieee754_nan (значення)) = справжній Успіх
u = 3,14, (is_ieee754_nan (значення)) = помилковий Успіх
w = inf, (is_ieee754_nan (значення)) = помилковий Успіх
[C: \ my \ forums \ so \ 282 (виявити NaN)]
> _
Підводячи підсумки вищезазначених результатів, лише пряме тестування представлення бітового рівня, використовуючи is_ieee754_nan
функцію, визначену в цій тестовій програмі, надійно працювало у всіх випадках і з g ++, і з Visual C ++.
Додавання:
Після публікації вище сказаного мені стало відомо про ще один можливий тест на NaN, згаданий в іншій відповіді тут, а саме ((value < 0) == (value >= 0))
. Це виявилося чудово працювати з Visual C ++, але не вдалося з -ffast-math
опцією g ++ . Надійно працює лише пряме тестування.