Kể từ C ++ 14, có một số cách để kiểm tra xem số dấu phẩy động value
có phải là NaN hay không.
Trong số các cách này, chỉ kiểm tra các bit của biểu diễn số, hoạt động đáng tin cậy, như đã lưu ý trong câu trả lời ban đầu của tôi. Cụ thể, std::isnan
và kiểm tra thường được đề xuất v != v
, không hoạt động đáng tin cậy và không nên được sử dụng, vì sợ rằng mã của bạn ngừng hoạt động chính xác khi ai đó quyết định rằng tối ưu hóa dấu phẩy động là cần thiết và yêu cầu trình biên dịch thực hiện điều đó. Tình huống này có thể thay đổi, trình biên dịch có thể trở nên phù hợp hơn, nhưng đối với vấn đề này đã không xảy ra trong 6 năm kể từ câu trả lời ban đầu.
Trong khoảng 6 năm, câu trả lời ban đầu của tôi là giải pháp được lựa chọn cho câu hỏi này, không sao cả. Nhưng gần đây, một câu trả lời được đánh giá cao khuyến nghị v != v
thử nghiệm không đáng tin cậy đã được chọn. Do đó, câu trả lời cập nhật hơn này (hiện tại chúng tôi có các tiêu chuẩn C ++ 11 và C ++ 14 và C ++ 17 ở đường chân trời).
Các cách chính để kiểm tra NaN-ness, kể từ C ++ 14, là:
std::isnan(value) )
là cách thư viện tiêu chuẩn dự định kể từ C ++ 11. isnan
rõ ràng mâu thuẫn với macro Posix cùng tên, nhưng thực tế đó không phải là vấn đề. Vấn đề chính là khi yêu cầu tối ưu hóa số học dấu phẩy động, sau đó với ít nhất một trình biên dịch chính, cụ thể là g ++, std::isnan
trả về false
cho đối số NaN .
(fpclassify(value) == FP_NAN) )
Đau khổ từ cùng một vấn đề như std::isnan
, không đáng tin cậy.
(value != value) )
Đề xuất trong nhiều câu trả lời SO. Đau khổ từ cùng một vấn đề như std::isnan
, không đáng tin cậy.
(value == Fp_info::quiet_NaN()) )
Đây là một thử nghiệm mà với hành vi tiêu chuẩn không nên phát hiện NaN, nhưng với hành vi được tối ưu hóa có thể phát hiện ra NaN (do mã được tối ưu hóa chỉ so sánh trực tiếp các biểu diễn bitlevel) và có thể kết hợp với một cách khác để che đậy hành vi không được tối ưu hóa tiêu chuẩn , có thể phát hiện đáng tin cậy NaN. Thật không may, hóa ra nó không hoạt động đáng tin cậy.
(ilogb(value) == FP_ILOGBNAN) )
Đau khổ từ cùng một vấn đề như std::isnan
, không đáng tin cậy.
isunordered(1.2345, value) )
Đau khổ từ cùng một vấn đề như std::isnan
, không đáng tin cậy.
is_ieee754_nan( value ) )
Đây không phải là một chức năng tiêu chuẩn. Đó là kiểm tra các bit theo tiêu chuẩn IEEE 754. Nó hoàn toàn đáng tin cậy nhưng mã phụ thuộc vào hệ thống.
Trong mã kiểm tra hoàn chỉnh sau đây, thành công, có phải là một biểu thức báo cáo giá trị Nan của giá trị hay không. Đối với hầu hết các biểu hiện, thước đo thành công này, mục tiêu phát hiện NaN và chỉ NaN, tương ứng với ngữ nghĩa chuẩn của chúng. (value == Fp_info::quiet_NaN()) )
Tuy nhiên, đối với biểu thức, hành vi tiêu chuẩn là nó không hoạt động như một máy dò 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 ) );
}
Kết quả với g ++ (lưu ý lại rằng hành vi tiêu chuẩn của (value == Fp_info::quiet_NaN())
nó là nó không hoạt động như một máy dò NaN, nó thực sự rất được quan tâm ở đây):
[C: \ my \ forum \ so \ 282 (phát hiện NaN)]
> g ++ - đảo ngược | tìm "++"
g ++ (x86_64-win32-sjlj-rev1, Được xây dựng bởi dự án MinGW-W64) 6.3.0
[C: \ my \ forum \ so \ 282 (phát hiện NaN)]
> g ++ foo.cpp && a
Trình biên dịch xác nhận IEEE 754 = true
v = nan, (std :: isnan (value)) = true Thành công
u = 3.14, (std :: isnan (value)) = false Thành công
w = inf, (std :: isnan (value)) = false Thành công
v = nan, ((fp classify (value) == 0x0100)) = true Thành công
u = 3.14, ((fp classify (value) == 0x0100)) = false Thành công
w = inf, ((fp classify (value) == 0x0100)) = false Thành công
v = nan, ((value! = value)) = true Thành công
u = 3.14, ((value! = value)) = false Thành công
w = inf, ((value! = value)) = false Thành công
v = nan, ((value == Fp_info :: quiet_NaN ())) = sai FAILED
u = 3.14, ((value == Fp_info :: quiet_NaN ())) = sai Thành công
w = inf, ((value == Fp_info :: quiet_NaN ())) = sai Thành công
v = nan, ((ilogb (value) == ((int) 0x80000000))) = thành công thực sự
u = 3.14, ((ilogb (value) == ((int) 0x80000000))) = sai Thành công
w = inf, ((ilogb (value) == ((int) 0x80000000))) = sai Thành công
v = nan, (được sắp xếp lại (1.2345, giá trị)) = Thành công thực sự
u = 3.14, (được sắp xếp lại (1.2345, giá trị)) = sai Thành công
w = inf, (được sắp xếp lại (1.2345, giá trị)) = sai Thành công
v = nan, (is_ieee754_nan (value)) = Thành công thực sự
u = 3.14, (is_ieee754_nan (value)) = sai Thành công
w = inf, (is_ieee754_nan (value)) = sai Thành công
[C: \ my \ forum \ so \ 282 (phát hiện NaN)]
> g ++ foo.cpp -ffast-math && a
Trình biên dịch xác nhận IEEE 754 = true
v = nan, (std :: isnan (value)) = sai FAILED
u = 3.14, (std :: isnan (value)) = false Thành công
w = inf, (std :: isnan (value)) = false Thành công
v = nan, ((fp classify (value) == 0x0100)) = FAILED sai
u = 3.14, ((fp classify (value) == 0x0100)) = false Thành công
w = inf, ((fp classify (value) == 0x0100)) = false Thành công
v = nan, ((giá trị! = giá trị)) = sai FAILED
u = 3.14, ((value! = value)) = false Thành công
w = inf, ((value! = value)) = false Thành công
v = nan, ((value == Fp_info :: quiet_NaN ())) = thành công thực sự
u = 3.14, ((value == Fp_info :: quiet_NaN ())) = FAILED thật
w = inf, ((value == Fp_info :: quiet_NaN ())) = FAILED thật
v = nan, ((ilogb (value) == ((int) 0x80000000))) = thành công thực sự
u = 3.14, ((ilogb (value) == ((int) 0x80000000))) = sai Thành công
w = inf, ((ilogb (value) == ((int) 0x80000000))) = sai Thành công
v = nan, (được sắp xếp lại (1.2345, giá trị)) = sai FAILED
u = 3.14, (được sắp xếp lại (1.2345, giá trị)) = sai Thành công
w = inf, (được sắp xếp lại (1.2345, giá trị)) = sai Thành công
v = nan, (is_ieee754_nan (value)) = Thành công thực sự
u = 3.14, (is_ieee754_nan (value)) = sai Thành công
w = inf, (is_ieee754_nan (value)) = sai Thành công
[C: \ my \ forum \ so \ 282 (phát hiện NaN)]
> _
Kết quả với Visual C ++:
[C: \ my \ forum \ so \ 282 (phát hiện NaN)]
> cl / nologo- 2> & 1 | tìm "++"
Trình biên dịch tối ưu hóa Microsoft (R) C / C ++ Phiên bản 19.00.23725 cho x86
[C: \ my \ forum \ so \ 282 (phát hiện NaN)]
> cl foo.cpp / Tháng 2 && b
foo.cpp
Trình biên dịch xác nhận IEEE 754 = true
v = nan, (std :: isnan (value)) = true Thành công
u = 3.14, (std :: isnan (value)) = false Thành công
w = inf, (std :: isnan (value)) = false Thành công
v = nan, ((fp classify (value) == 2)) = true Thành công
u = 3.14, ((fp classify (value) == 2)) = false Thành công
w = inf, ((fp classify (value) == 2)) = false Thành công
v = nan, ((value! = value)) = true Thành công
u = 3.14, ((value! = value)) = false Thành công
w = inf, ((value! = value)) = false Thành công
v = nan, ((value == Fp_info :: quiet_NaN ())) = sai FAILED
u = 3.14, ((value == Fp_info :: quiet_NaN ())) = sai Thành công
w = inf, ((value == Fp_info :: quiet_NaN ())) = sai Thành công
v = nan, ((ilogb (value) == 0x7fffffff)) = Thành công thực sự
u = 3.14, ((ilogb (giá trị) == 0x7fffffff)) = sai Thành công
w = inf, ((ilogb (value) == 0x7fffffff)) = FAILED thật
v = nan, (được sắp xếp lại (1.2345, giá trị)) = Thành công thực sự
u = 3.14, (được sắp xếp lại (1.2345, giá trị)) = sai Thành công
w = inf, (được sắp xếp lại (1.2345, giá trị)) = sai Thành công
v = nan, (is_ieee754_nan (value)) = Thành công thực sự
u = 3.14, (is_ieee754_nan (value)) = sai Thành công
w = inf, (is_ieee754_nan (value)) = sai Thành công
[C: \ my \ forum \ so \ 282 (phát hiện NaN)]
> cl foo.cpp / Tháng 2 / fp: nhanh && b
foo.cpp
Trình biên dịch xác nhận IEEE 754 = true
v = nan, (std :: isnan (value)) = true Thành công
u = 3.14, (std :: isnan (value)) = false Thành công
w = inf, (std :: isnan (value)) = false Thành công
v = nan, ((fp classify (value) == 2)) = true Thành công
u = 3.14, ((fp classify (value) == 2)) = false Thành công
w = inf, ((fp classify (value) == 2)) = false Thành công
v = nan, ((value! = value)) = true Thành công
u = 3.14, ((value! = value)) = false Thành công
w = inf, ((value! = value)) = false Thành công
v = nan, ((value == Fp_info :: quiet_NaN ())) = sai FAILED
u = 3.14, ((value == Fp_info :: quiet_NaN ())) = sai Thành công
w = inf, ((value == Fp_info :: quiet_NaN ())) = sai Thành công
v = nan, ((ilogb (value) == 0x7fffffff)) = Thành công thực sự
u = 3.14, ((ilogb (giá trị) == 0x7fffffff)) = sai Thành công
w = inf, ((ilogb (value) == 0x7fffffff)) = FAILED thật
v = nan, (được sắp xếp lại (1.2345, giá trị)) = Thành công thực sự
u = 3.14, (được sắp xếp lại (1.2345, giá trị)) = sai Thành công
w = inf, (được sắp xếp lại (1.2345, giá trị)) = sai Thành công
v = nan, (is_ieee754_nan (value)) = Thành công thực sự
u = 3.14, (is_ieee754_nan (value)) = sai Thành công
w = inf, (is_ieee754_nan (value)) = sai Thành công
[C: \ my \ forum \ so \ 282 (phát hiện NaN)]
> _
Tóm tắt các kết quả trên, chỉ kiểm tra trực tiếp biểu diễn mức bit, sử dụng is_ieee754_nan
hàm được xác định trong chương trình thử nghiệm này, hoạt động đáng tin cậy trong mọi trường hợp với cả g ++ và Visual C ++.
Phụ lục:
Sau khi đăng bài ở trên, tôi đã biết về một khả năng khác để kiểm tra NaN, được đề cập trong một câu trả lời khác ở đây, cụ thể là ((value < 0) == (value >= 0))
. Điều đó hóa ra hoạt động tốt với Visual C ++ nhưng không thành công với -ffast-math
tùy chọn của g ++ . Chỉ kiểm tra bitpotype trực tiếp hoạt động đáng tin cậy.