在c++中检查double(或float)是否为NaN

是否存在isnan()函数?

附注:我在MinGW(如果这有区别的话)。

我通过使用<math.h>中的isnan()解决了这个问题,它在<cmath>中不存在,我一开始是#includeing。

434434 次浏览

如果你的编译器支持c99扩展,有一个std::isnan,但我不确定mingw是否支持。

下面是一个小函数,如果你的编译器没有标准函数,它应该可以工作:

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

根据IEEE标准,NaN值有一个奇怪的属性,涉及它们的比较是总是 false。也就是说,对于浮点数f,如果f为NaN, f != f将为真只有

请注意,正如下面的一些评论所指出的,并不是所有编译器在优化代码时都尊重这一点。

对于任何声称使用IEEE浮点数的编译器,这个技巧应该都有效。但是我不能保证它在实践中有效。如果有疑问,请检查编译器。

你可以使用isnan()函数,但是你需要包含C数学库。

#include <cmath>

因为这个函数是C99的一部分,所以并不是所有地方都可用。如果您的供应商没有提供该功能,您也可以定义自己的变体以实现兼容性。

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

你可以使用limits标准库中定义的numeric_limits<float>::quiet_NaN( )进行测试。为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;
}

我不知道这是否适用于所有平台,因为我只在Linux上用g++进行了测试。

Boost中还有一个只包括头文件的库,它有一些简洁的工具来处理浮点数据类型

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

你会得到以下函数:

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);

如果你有时间,那么看看Boost的整个数学工具包,它有许多有用的工具,并且正在快速增长。

另外,当处理浮点和非浮点时,查看数字转换可能是一个好主意。

当前c++标准库中没有isnan()函数可用。它是在C99中引入的,并定义为而不是函数。C99定义的标准库元素既不是当前c++标准ISO/IEC 14882:1998的一部分,也不是更新版ISO/IEC 14882:2003的一部分。

2005年提出了技术报告1。TR1为c++带来了与C99的兼容性。尽管它从未被正式采用成为c++标准,但许多(GCC 4.0 +Visual c++ 9.0+ c++实现确实提供了TR1特性,全部或仅部分(Visual c++ 9.0不提供C99数学函数)。

如果TR1可用,则cmath包含C99元素,如isnan()isfinite()等,但它们被定义为函数,而不是宏,通常在std::tr1::命名空间中,尽管许多实现(例如Linux上的GCC 4+或Mac OS X 10.5+上的XCode)将它们直接注入std::,因此std::isnan定义良好。

此外,c++的一些实现仍然使C99 isnan()宏对c++可用(通过cmathmath.h包含),这可能会引起更多的混淆,开发人员可能认为这是一种标准行为。

关于visualc++的一个注意事项,如上所述,它不提供std::isnanstd::tr1::isnan,但它提供了一个定义为_isnan()的扩展函数,该扩展函数自Visual c++ 6.0以来一直可用

在XCode中,有更多的乐趣。如前所述,GCC 4+定义了std::isnan。对于旧版本的编译器和库形式的XCode,似乎(这里是相关的讨论),我还没有机会检查)定义了两个函数,英特尔上的__inline_isnand()和Power PC上的__isnand()

有三种“正式”方法:posix isnan、c++0x isnan函数模板或visual c++ _isnan函数

不幸的是,要检测使用哪一种是相当不切实际的。

不幸的是,没有可靠的方法来检测是否有IEEE 754表示的nan。标准库提供了一种官方的方法(numeric_limits<double>::is_iec559)。但在实践中,g++之类的编译器会搞砸这一点。

理论上可以简单地使用x != x,但g++和visual c++之类的编译器会把它搞砸。

因此,最后,测试特定的南bitpatterns,假设(并希望在某些时候强制执行!)一个特定的表示,例如IEEE 754。


编辑:作为编译器g++ …去他的”,考虑一下

#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 );
}

使用g++ (TDM-2 mingw32) 4.4.1编译:

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


C:\test> gnuc x.cpp


C:\test> a && echo works... || echo !failed
works...


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


C:\test> a && echo works... || echo !failed
Assertion failed: a != b, file x.cpp, line 6


This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
!failed


C:\test> _

下面的代码使用NAN(所有指数位集合,至少一个小数位集合)的定义,并假设sizeof(int) = sizeof(float) = 4。你可以在维基百科中查找NAN的详细信息。

bool IsNan(浮点值) { return ((*(UINT*)&value) &0 x7fffffff)比;0 x7f800000; } < /代码> < / p >

一个可能的解决方案,不依赖于特定的IEEE表示NaN使用如下:

template<class T>
bool isnan( T f ) {
T _nan =  (T)0.0/(T)0.0;
return 0 == memcmp( (void*)&f, (void*)&_nan, sizeof(T) );
}
对我来说,解决方案可以是一个宏,使它显式内联,从而足够快。 它也适用于任何浮点类型。它基于这样一个事实:一个值不等于本身的唯一情况是当该值不是一个数字时
#ifndef isnan
#define isnan(a) (a != a)
#endif

第一个解决方案:如果您使用c++ 11

既然问了这个问题,就有了一些新的发展:知道std::isnan()是c++ 11的一部分是很重要的

剧情简介

在头文件<cmath>中定义

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

确定给定的浮点数参数是否为非数字(NaN)。

参数

arg:浮点值

返回值

如果arg是NaNtrue,否则是false

参考

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

请注意,如果您使用g++,这与-fast-math不兼容,请参阅下面的其他建议。


其他解决方案:如果你使用非c++ 11兼容的工具

对于C99,在C语言中,这是作为返回int值的宏__abc0实现的。x的类型应该是float, double或long double。

不同的供应商可能包含或不包含函数isnan()

检查NaN的据称可移植的方法是使用IEEE 754属性NaN不等于自身:即x == x将为false,因为xNaN

然而,最后一个选项可能不适用于每个编译器和某些设置(特别是优化设置),所以在最后的手段,你总是可以检查位模式…

如上所述,a != a在g++和其他一些编译器中不起作用,但这个技巧应该起作用。它可能没有那么高效,但它仍然是一种方法:

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

基本上,在g++中(虽然我不确定其他)printf在%d或%上打印'nan'。如果变量不是有效的整数/浮点数,则格式为F。因此,这段代码检查字符串的第一个字符是否为“n”(如“nan”)

南预防

我对这个问题的答案是不要对nan使用追溯检查。使用预防检查形式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.

nan0.f/0.f0.0/0.0操作的结果。nan是你的代码稳定性的一个可怕的克星,必须非常小心地检测和预防nan不同于普通数字的属性:

  • nan是有毒的,(5*nan=nan)
  • nan不等于任何东西,甚至不等于它本身(nan != nan)
  • nan不大于任何值(nan !>0)
  • nan不小于任何东西(nan !<0)

最后列出的2个属性是反逻辑的,将导致依赖于与nan数字比较的代码的奇怪行为(最后3个属性也是奇怪的,但你可能永远不会在你的代码中看到x != x ?(除非你正在检查nan(不可靠)))。

在我自己的代码中,我注意到nan值往往会产生难以发现的错误。(注意这是,而不是inf-inf。(-inf & lt;返回TRUE, (0 <inf)返回TRUE,甚至(-inf <inf)返回TRUE。因此,根据我的经验,代码的行为是经常仍然如所愿)。

在奶奶手下该怎么办

你想在0.0/0.0 必须作为特例处理吗下发生什么,但你所做的必须取决于你期望从代码中得到的数字。

在上面的例子中,(0.f/FLT_MIN)的结果基本上是0。你可能想让0.0/0.0生成HUGE。所以,

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.

所以在上面,如果x是0.f,则会产生inf(实际上如上所述,它具有相当好的/非破坏性行为)。

记住,整数除0会导致运行时异常。所以你必须总是检查整数除以0。仅仅因为0.0/0.0悄悄地计算为nan并不意味着你可以懒惰,在发生0.0/0.0之前不检查它。

1 通过__ABC1对__ABC0的检查有时是不可靠的(__ABC1被一些优化编译器剥离,这违反了IEEE法规,特别是当-ffast-math开关被启用时)。

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);
}

如果sizeof(int)为4,而sizeof(long long)为8,则此方法有效。

在运行时,它只是比较,类型转换不需要任何时间。它只是改变比较标志配置来检查是否相等。

IEEE标准说 当指数都是__abc时 而且 尾数不是零, 该数字是NaN。 Double是1符号位,11指数位和52尾数位。

如此:

#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;
}

输出:isnan

考虑到(x != x)对于NaN并不总是保证的(比如如果使用- fast-math选项),我一直在使用:

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

数字不能兼而有之。0和>= 0,所以这个检查只有在数字既不小于,也不大于或等于0时才通过。基本上没有数字,或者NaN。

如果你喜欢,你也可以使用这个:

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

我不确定这是如何受到快速数学的影响,所以你的里程可能会有所不同。

在我看来,最好的真正跨平台的方法是使用联合,并测试double的位模式来检查nan。

我还没有彻底测试这个解决方案,可能有一种更有效的方法来处理比特模式,但我认为它应该有效。

#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;
}

从c++ 14开始,有很多方法来测试浮点数value是否是NaN。

在这些方式中,只有位的检查的数字表示, 正如我最初的答案所指出的那样,工作可靠。特别是,std::isnan和经常被建议的检查v != v工作不可靠,不应该使用,以免当有人决定需要浮点优化并要求编译器这样做时,您的代码停止正确工作。这种情况可以改变,编译器可以变得更加一致,但对于这个问题,自最初的答案以来,6年都没有发生过

在大约6年的时间里,我最初的答案是这个问题的选择解决方案,这是OK的。但是最近一个高度好评的答案推荐不可靠的v != v测试已经被选中。因此,这是一个额外的最新答案(我们现在有c++ 11和c++ 14标准,以及即将出现的c++ 17标准)。


从c++ 14开始,检查nan属性的主要方法是:

    <李> < p > std::isnan(value) ) < br > 是自c++ 11以来预期的标准库方式。isnan显然与 Posix宏的同名,但在实践中这不是问题。主要问题是 当请求浮点算法优化时,则使用至少一个主编译器,即g++, std::isnan NaN参数返回false.

    <李> < p > (fpclassify(value) == FP_NAN) ) < br > 遇到与std::isnan相同的问题,即不可靠 <李> < p > (value != value) ) < br > 在许多SO答案中推荐。遇到与std::isnan相同的问题,即,

    <李> < p > (value == Fp_info::quiet_NaN()) ) < br > 这个测试用标准行为不应该检测nan,但是用 优化的行为可能可以检测到nan(由于优化的代码只是比较 位级表示直接),并可能与另一种方法相结合 覆盖标准的未优化行为,能够可靠地检测NaN。不幸的是

    <李> < p > (ilogb(value) == FP_ILOGBNAN) ) < br > 遇到与std::isnan相同的问题,即不可靠 <李> < p > isunordered(1.2345, value) ) < br > 遇到与std::isnan相同的问题,即不可靠 <李> < p > is_ieee754_nan( value ) ) < br > 这不是一个标准函数。它是根据IEEE 754来检查比特的 标准。这是完全可靠的,代码有点依赖于系统

在以下完整的测试代码“success”表达式是否报告值的nan性。对于大多数表达式来说,这种成功的度量,即检测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  (detect NaN)]
> g++ --version | find "++"
g++ (x86_64-win32-sjlj-rev1, Built by MinGW-W64 project) 6.3.0


[C:\my\forums\so\282  (detect NaN)]
> g++ foo.cpp && a
Compiler claims IEEE 754 = true


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


v = nan, ((fpclassify(value) == 0x0100)) = true               Success
u = 3.14, ((fpclassify(value) == 0x0100)) = false             Success
w = inf, ((fpclassify(value) == 0x0100)) = false              Success


v = nan, ((value != value)) = true                            Success
u = 3.14, ((value != value)) = false                          Success
w = inf, ((value != value)) = false                           Success


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


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


v = nan, (isunordered(1.2345, value)) = true                  Success
u = 3.14, (isunordered(1.2345, value)) = false                Success
w = inf, (isunordered(1.2345, value)) = false                 Success


v = nan, (is_ieee754_nan( value )) = true                     Success
u = 3.14, (is_ieee754_nan( value )) = false                   Success
w = inf, (is_ieee754_nan( value )) = false                    Success


[C:\my\forums\so\282  (detect NaN)]
> g++ foo.cpp -ffast-math && a
Compiler claims IEEE 754 = true


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


v = nan, ((fpclassify(value) == 0x0100)) = false              FAILED
u = 3.14, ((fpclassify(value) == 0x0100)) = false             Success
w = inf, ((fpclassify(value) == 0x0100)) = false              Success


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


v = nan, ((value == Fp_info::quiet_NaN())) = true             Success
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         Success
u = 3.14, ((ilogb(value) == ((int)0x80000000))) = false       Success
w = inf, ((ilogb(value) == ((int)0x80000000))) = false        Success


v = nan, (isunordered(1.2345, value)) = false                 FAILED
u = 3.14, (isunordered(1.2345, value)) = false                Success
w = inf, (isunordered(1.2345, value)) = false                 Success


v = nan, (is_ieee754_nan( value )) = true                     Success
u = 3.14, (is_ieee754_nan( value )) = false                   Success
w = inf, (is_ieee754_nan( value )) = false                    Success


[C:\my\forums\so\282  (detect NaN)]
> _

Visual c++测试结果:

[C:\my\forums\so\282  (detect NaN)]
> cl /nologo- 2>&1 | find "++"
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.23725 for x86


[C:\my\forums\so\282  (detect NaN)]
> cl foo.cpp /Feb && b
foo.cpp
Compiler claims IEEE 754 = true


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


v = nan, ((fpclassify(value) == 2)) = true                    Success
u = 3.14, ((fpclassify(value) == 2)) = false                  Success
w = inf, ((fpclassify(value) == 2)) = false                   Success


v = nan, ((value != value)) = true                            Success
u = 3.14, ((value != value)) = false                          Success
w = inf, ((value != value)) = false                           Success


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


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


v = nan, (isunordered(1.2345, value)) = true                  Success
u = 3.14, (isunordered(1.2345, value)) = false                Success
w = inf, (isunordered(1.2345, value)) = false                 Success


v = nan, (is_ieee754_nan( value )) = true                     Success
u = 3.14, (is_ieee754_nan( value )) = false                   Success
w = inf, (is_ieee754_nan( value )) = false                    Success


[C:\my\forums\so\282  (detect NaN)]
> cl foo.cpp /Feb /fp:fast && b
foo.cpp
Compiler claims IEEE 754 = true


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


v = nan, ((fpclassify(value) == 2)) = true                    Success
u = 3.14, ((fpclassify(value) == 2)) = false                  Success
w = inf, ((fpclassify(value) == 2)) = false                   Success


v = nan, ((value != value)) = true                            Success
u = 3.14, ((value != value)) = false                          Success
w = inf, ((value != value)) = false                           Success


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


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


v = nan, (isunordered(1.2345, value)) = true                  Success
u = 3.14, (isunordered(1.2345, value)) = false                Success
w = inf, (isunordered(1.2345, value)) = false                 Success


v = nan, (is_ieee754_nan( value )) = true                     Success
u = 3.14, (is_ieee754_nan( value )) = false                   Success
w = inf, (is_ieee754_nan( value )) = false                    Success


[C:\my\forums\so\282  (detect NaN)]
> _

综上所述,仅使用本测试程序中定义的is_ieee754_nan函数直接测试位级表示,在g++和Visual c++的所有情况下都可靠地工作。


< p >附录:< br > 在发布以上内容后,我意识到另一种可能的NaN测试,在这里的另一个答案中提到,即((value < 0) == (value >= 0))。这在Visual c++中工作得很好,但在g++的-ffast-math选项中失败了。只有直接的位模式测试才能可靠地工作

这可以在Visual Studio中通过检查它是否在双重限制范围内来检测无穷大和NaN:

//#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;

在x86-64上,你可以有非常快速的方法来检查NaN和无穷大,不管-ffast-math编译器选项如何工作。(f != fstd::isnanstd::isinf总是生成false-ffast-math)。


NaN、无穷大和有限数的测试可以通过检查最大指数轻松完成。无穷大是最大指数和零尾数,NaN是最大指数和非零尾数。指数存储在最上面的符号位之后的下一位中,这样我们就可以左移来去掉符号位,使指数成为最上面的位,不需要屏蔽(operator&):

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; }

isinfisfinitestd版本从.data段加载2个double/float常量,在最坏的情况下,它们会导致2个数据缓存失败。上述版本不加载任何数据,inf_double_shl1inf_float_shl1常量被编码为立即操作数进入汇编指令。


更快的isnan2只是2个汇编指令:

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;
}

如果参数为NaN,则使用ucomisd指令设置奇偶校验标志的事实。这就是在没有指定-ffast-math选项时std::isnan的工作方式。