nan
代码中的更好。nan
如果对程序有害,它可能会给您的程序造成极大的破坏,它可能导致难以发现的错误。这是因为nan
有毒(5 * nan
= nan
)nan
不等于(nan
!= nan
),nan
不大于(nan
!> 0),nan
不小于(nan
!<0)。
nan
代码中的更好。nan
如果对程序有害,它可能会给您的程序造成极大的破坏,它可能导致难以发现的错误。这是因为nan
有毒(5 * nan
= nan
)nan
不等于(nan
!= nan
),nan
不大于(nan
!> 0),nan
不小于(nan
!<0)。
Answers:
根据IEEE标准,NaN值具有奇数性质,即涉及它们的比较始终为假。也就是说,对于浮点数f,仅当f为NaN 时才f != f
为真。
请注意,正如下面的一些评论所指出的,并不是所有的编译器在优化代码时都遵守这一点。
对于声称使用IEEE浮点的任何编译器,此技巧都应该起作用。但是我不能保证它会在实践中起作用。如有疑问,请与您的编译器联系。
-ffast-math
选项的文档明确表示,如果数学函数的IEEE或ISO规则/规范依赖于确切的实现,则可能导致程序输出不正确。如果未启用该选项,则使用x != x
NaN是一种非常有效且可移植的NaN测试方法。
x != x
没有该选项的结论是不合逻辑的。特定版本的g ++可能适用,也可能不适用。无论如何,您通常无法保证不会使用fastmath选项。
-ffast-math
选项),那么这x != x
是一种有效且可移植的解决方案。您甚至可以-ffast-math
通过测试__FAST_MATH__
宏来进行测试,并在这种情况下切换到其他实现(例如,使用联合和位旋转)。
isnan()
当前的C ++标准库中没有可用的功能。它在C99中引入,并定义为宏而不是函数。C99定义的标准库的元素不是当前C ++标准ISO / IEC 14882:1998的一部分,也不是其更新ISO / IEC 14882:2003的一部分。
在2005年提出了技术报告1。TR1将与C99的兼容性引入C ++。尽管事实上它从未被正式采纳为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 ++(通过cmath
或包含math.h
),这可能引起更多的混乱,并且开发人员可能会认为这是标准行为。
约Viusal C A注++,如上所述,它不提供std::isnan
既不std::tr1::isnan
,但它提供了定义为一个扩展函数_isnan()
这一直是因为现有的Visual C ++ 6.0
在XCode上,还有更多的乐趣。如前所述,GCC 4+定义std::isnan
。对于较旧版本的编译器和库形式的XCode,似乎(这里是相关讨论,还没有机会检查一下自己)__inline_isnand()
在Intel和__isnand()
Power PC上定义了两个函数。
std::isnan
现在已成为C ++ 11标准的一部分,并且支持范围已经扩展,因此应该更新此答案。从Visual Studio 2013开始,在Visual Studio中实现了std :: isnan。也许@shuhalo负责了:-)
自从有人问到这个问题以来,就出现了一些新的发展:重要的是要知道这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)
确定给定的浮点数arg是否不是数字(NaN
)。
参量
arg
:浮点值
返回值
true
如果arg是NaN
,false
否则
参考
http://en.cppreference.com/w/cpp/numeric/math/isnan
请注意,如果您使用g ++,则它与-fast-math不兼容,请参见以下其他建议。
对于C99,在C中,这被实现为isnan(c)
返回int值的宏。的类型x
应为float,double或long double。
各种供应商可能包含或不包含功能isnan()
。
要检查理应可移植的方法NaN
是使用IEEE 754属性,NaN
不等于本身:即x == x
将是错误的x
是NaN
。
但是,最后一个选项可能不适用于所有编译器和某些设置(尤其是优化设置),因此,在万不得已时,您始终可以检查位模式...
std::isnan
截至2017年2月,-1仍然不是一个好的建议,因为它不适用于g ++的浮点优化。
x != x
和isnan
需要的IEEE 754合规工作。关于后者,IEEE 754-2008标准声明“实施应为所有支持的算术格式提供以下非计算操作”,并且“当且仅当x是NaN时,isNaN(x)才为真”。为了检查标准要求is754version1985()
和的一致性is754version2008()
,而C ++提供了std::numeric_limits<Fp>::is_iec559()
(IEC 559是同一标准)。不幸的是,通过-ffast-math
优化,例如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的整个Math工具箱,它具有许多有用的工具并且正在迅速发展。
另外,在处理浮点数和非浮点数时,最好查看数字转换。
有三种“官方”方式:posix isnan
宏,c ++ 0x isnan
函数模板或可视c ++ _isnan
函数。
不幸的是,检测使用哪个是不切实际的。
不幸的是,没有可靠的方法来检测您是否具有带有NaN的IEEE 754表示形式。标准库提供了一种官方的方法(numeric_limits<double>::is_iec559
)。但是实际上,诸如g ++之类的编译器会搞砸了。
从理论上讲,可以简单地使用x != x
,但是g ++和visual c ++之类的编译器可以解决这一问题。
因此,最后,测试特定的NaN位模式,并假设(并希望在某些时候实施!)特定的表示形式,例如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>键入“ 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> &&回声有效... || 回声!失败 作品... C:\ test> gnuc x.cpp --fast-math C:\ test> &&回声有效... || 回声!失败 断言失败:a!= b,文件x.cpp,第6行 该应用程序已请求运行时以一种异常方式终止它。 请与应用程序的支持团队联系以获取更多信息。 失败 C:\ test> _
-ffast-math
选项的文档明确指出,如果数学函数的IEEE或ISO规则/规范依赖于确切的实现,则可能导致程序输出不正确。如果未启用该选项,则使用x != x
NaN是一种非常有效且可移植的NaN测试方法。
gcc -ffast-math
它仍然是符合标准的C ++实现(嗯,假设它numeric_limits::is_iec559
是对的,尽管Alf在上面提出了建议,但事实并非如此):依赖IEEE的C ++代码不是可移植的C ++,并且没有权利期望实现能够提供它。
is_iec559
确实如此-ffast-math
。因此,这里的问题在于,GCC的文档-ffast-math
仅说它是用于数学函数的非IEEE / ISO,而他们应该说它是非C ++,因为它的实现numeric_limits
很糟糕。我猜想,GCC不能总是在定义模板时告诉您最终的后端是否实际上具有符合要求的浮点数,因此甚至都不会尝试。IIRC在GCC的C99一致性未解决的错误列表中也存在类似的问题。
is_iec559
是否需要IEEE,实际上在GCC上似乎不起作用。C ++ 0x确实具有isnan
功能,但是由于GCC is_iec559
现在无法正确实现,我想它也不会在C ++ 0x中使用,并且很-ffast-math
可能破坏它的isnan
。
如果您的编译器支持c99扩展,则有一个std :: isnan,但是我不确定mingw是否支持。
如果您的编译器没有标准功能,这是一个小功能,应该可以使用:
bool custom_isnan(double var)
{
volatile double d = var;
return d != d;
}
isnan
返回错误的结果。从技术上讲,编译器可能有错误,但实际上,不会发生。与相同var != var
。之所以有效,是因为这就是IEEE浮点值的定义方式。
您可以使用标准库中numeric_limits<float>::quiet_NaN( )
定义的内容limits
进行测试。为定义了一个单独的常量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 ++进行了测试。
您可以使用该isnan()
函数,但需要包括C数学库。
#include <cmath>
由于此功能是C99的一部分,因此并非在所有地方都可用。如果您的供应商不提供该功能,则也可以定义自己的变体以实现兼容性。
inline bool isnan(double x) {
return x != x;
}
isnan
在<math.h>中
以下代码使用NAN的定义(所有指数位集,至少一个小数位集),并假定sizeof(int)= sizeof(float)=4。有关详细信息,您可以在Wikipedia中查找NAN。
bool IsNan( float value )
{
return ((*(UINT*)&value) & 0x7fffffff) > 0x7f800000;
}
0x7fffffff
只会简单地放在内存中ff ff ff 7f
。 value
具有与相同的顺序0x7f800000
,因此所有操作都排成一行(没有字节交换)。我想知道是否有人可以在大型endian平台上对此进行测试。
0x7fff1234
也是NaN。所以是0xffffffff
我对这个问题的回答是不要对进行追溯检查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.
nan
操作的结果0.f/0.f
,或0.0/0.0
。 nan
对您的代码的稳定性是一个可怕的敌人,必须非常仔细地检测并防止它的出现1。其属性nan
与普通数字不同:
nan
有毒,(5 * nan
=nan
)nan
不等于任何东西,甚至不等于本身(nan
!=nan
)nan
不大于任何东西(nan
!> 0)nan
不小于(nan
!<0)列出的最后2个属性是反逻辑的,将导致依赖于与nan
数字进行比较的代码的行为异常(最后3个属性也是奇数,但您可能永远不会看到x != x ?
在代码中(除非您正在检查)对于nan(不可靠)))。
在我自己的代码中,我注意到nan
值往往会产生难以发现的错误。(请注意,对于或,情况并非如此。(<0)返回,(0 < )返回TRUE,甚至(< )返回TRUE。因此,以我的经验,代码的行为通常是inf
-inf
-inf
TRUE
inf
-inf
inf
仍然是所需的。)
您想发生的事情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 检查过nan
孔x != x
有时是不可靠的(x != x
被某些优化的编译器所剥夺,这些编译器破坏了IEEE的合规性,尤其-ffast-math
是在启用开关时)。
从C ++ 14开始,有多种方法可以测试浮点数value
是否为NaN。
在这些方式中,仅检查数字表示的位数是可靠的,如我最初的回答所述。特别std::isnan
是经常提出的检查v != v
不能可靠地工作并且不应使用,以免在有人确定需要浮点优化并要求编译器执行此操作时,您的代码停止正确工作。这种情况可能会改变,编译器会变得更加合规,但是对于这个问题,自原始答案以来的6年中还没有发生过。
在大约6年的时间里,我最初的答案是为此问题选择的解决方案,没问题。但是最近选择了一个高度推荐的答案,建议该v != v
测试不可靠。因此,这个额外的最新答案(我们现在有了C ++ 11和C ++ 14标准以及即将出现的C ++ 17)。
从C ++ 14开始,检查NaN-ness的主要方法是:
std::isnan(value) )
自C ++ 11起就是标准的库方法。isnan
显然与同名的Posix宏发生冲突,但是实际上这不是问题。主要问题是,当请求浮点算术优化时,然后至少使用一个主编译器(即g ++)为NaN参数std::isnan
返回false
。
(fpclassify(value) == FP_NAN) )
遭受与相同的问题std::isnan
,即不可靠。
(value != value) )
在许多SO答案中都推荐使用。遭受与相同的问题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 ++ --version | 找到“ ++” g ++(x86_64-win32-sjlj-rev1,由MinGW-W64项目构建)6.3.0 [C:\ my \ forums \ so \ 282(检测NaN)] > g ++ foo.cpp &&一个 编译器声称IEEE 754 = true v = nan,(std :: isnan(value))= true成功 u = 3.14,(std :: isnan(value))=假成功 w = inf,(std :: isnan(value))=假成功 v = nan,(((fpclassify(value)== 0x0100))= true成功 u = 3.14,([[fpclassify(value)== 0x0100))=否成功 w = inf,([[fpclassify(value)== 0x0100))=错误成功 v = nan,(((value!= value))= true成功 u = 3.14,([[value!= value))=否成功 w = inf,(((value!= value))= false成功 v = nan,(((值== Fp_info :: quiet_NaN()))=假失败 u = 3.14,(((value == Fp_info :: quiet_NaN()))=错误成功 w = inf,(((value == Fp_info :: quiet_NaN()))=错误成功 v = nan,(((ilogb(value)==((int)0x80000000)))= true成功 u = 3.14,(((ilogb(value)==((int)0x80000000)))=错误成功 w = inf,(((ilogb(value)==((int)0x80000000)))=错误成功 v = nan,(isunordered(1.2345,value))= true成功 u = 3.14,(isunordered(1.2345,value))=否成功 w = inf,(isunordered(1.2345,value))= false成功 v = nan,(is_ieee754_nan(value))= true成功 u = 3.14,(is_ieee754_nan(value))= false成功 w = inf,(is_ieee754_nan(value))= false成功 [C:\ my \ forums \ so \ 282(检测NaN)] > g ++ foo.cpp -ffast-math && a 编译器声称IEEE 754 = true v = nan,(std :: isnan(value))=假失败 u = 3.14,(std :: isnan(value))=假成功 w = inf,(std :: isnan(value))=假成功 v = nan,([[fpclassify(value)== 0x0100))=错误失败 u = 3.14,([[fpclassify(value)== 0x0100))=否成功 w = inf,([[fpclassify(value)== 0x0100))=错误成功 v = nan,(((值!=值)))=假失败 u = 3.14,([[value!= value))=否成功 w = inf,(((value!= value))= false成功 v = nan,(((value == Fp_info :: quiet_NaN()))= true成功 u = 3.14,(((value == Fp_info :: quiet_NaN()))= true失败 w = inf,(((value == Fp_info :: quiet_NaN()))= true失败 v = nan,(((ilogb(value)==((int)0x80000000)))= true成功 u = 3.14,(((ilogb(value)==((int)0x80000000)))=错误成功 w = inf,(((ilogb(value)==((int)0x80000000)))=错误成功 v = nan,(isunordered(1.2345,value))=假失败 u = 3.14,(isunordered(1.2345,value))=否成功 w = inf,(isunordered(1.2345,value))= false成功 v = nan,(is_ieee754_nan(value))= true成功 u = 3.14,(is_ieee754_nan(value))= false成功 w = inf,(is_ieee754_nan(value))= false成功 [C:\ my \ forums \ so \ 282(检测NaN)] > _
使用Visual C ++的结果:
[C:\ my \ forums \ so \ 282(检测NaN)] > cl / nologo- 2>&1 | 找到“ ++” 适用于x86的Microsoft(R)C / C ++优化编译器版本19.00.23725 [C:\ my \ forums \ so \ 282(检测NaN)] > cl foo.cpp / Feb && b foo.cpp 编译器声称IEEE 754 = true v = nan,(std :: isnan(value))= true成功 u = 3.14,(std :: isnan(value))=假成功 w = inf,(std :: isnan(value))=假成功 v = nan,(((fpclassify(value)== 2))=真成功 u = 3.14,([[fpclassify(value)== 2))=错误成功 w = inf,([[fpclassify(value)== 2))=错误成功 v = nan,(((value!= value))= true成功 u = 3.14,([[value!= value))=否成功 w = inf,(((value!= value))= false成功 v = nan,(((值== Fp_info :: quiet_NaN()))=假失败 u = 3.14,(((value == Fp_info :: quiet_NaN()))=错误成功 w = inf,(((value == Fp_info :: quiet_NaN()))=错误成功 v = nan,(((ilogb(value)== 0x7fffffff))=真成功 u = 3.14,(((ilogb(value)== 0x7fffffff)))=错误成功 w = inf,(((ilogb(value)== 0x7fffffff))=真失败 v = nan,(isunordered(1.2345,value))= true成功 u = 3.14,(isunordered(1.2345,value))=否成功 w = inf,(isunordered(1.2345,value))= false成功 v = nan,(is_ieee754_nan(value))= true成功 u = 3.14,(is_ieee754_nan(value))= false成功 w = inf,(is_ieee754_nan(value))= false成功 [C:\ my \ forums \ so \ 282(检测NaN)] > cl foo.cpp / Feb / fp:fast && b foo.cpp 编译器声称IEEE 754 = true v = nan,(std :: isnan(value))= true成功 u = 3.14,(std :: isnan(value))=假成功 w = inf,(std :: isnan(value))=假成功 v = nan,(((fpclassify(value)== 2))=真成功 u = 3.14,([[fpclassify(value)== 2))=错误成功 w = inf,([[fpclassify(value)== 2))=错误成功 v = nan,(((value!= value))= true成功 u = 3.14,([[value!= value))=否成功 w = inf,(((value!= value))= false成功 v = nan,(((值== Fp_info :: quiet_NaN()))=假失败 u = 3.14,(((value == Fp_info :: quiet_NaN()))=错误成功 w = inf,(((value == Fp_info :: quiet_NaN()))=错误成功 v = nan,(((ilogb(value)== 0x7fffffff))=真成功 u = 3.14,(((ilogb(value)== 0x7fffffff)))=错误成功 w = inf,(((ilogb(value)== 0x7fffffff))=真失败 v = nan,(isunordered(1.2345,value))= true成功 u = 3.14,(isunordered(1.2345,value))=否成功 w = inf,(isunordered(1.2345,value))= false成功 v = nan,(is_ieee754_nan(value))= true成功 u = 3.14,(is_ieee754_nan(value))= false成功 w = inf,(is_ieee754_nan(value))= false成功 [C:\ my \ forums \ so \ 282(检测NaN)] > _
总结以上结果,仅使用is_ieee754_nan
此测试程序中定义的功能对位级表示形式进行直接测试,在所有情况下均适用于g ++和Visual C ++。
附录:
发布以上内容后,我意识到可以测试NaN的另一种可能性,在这里的另一个答案中提到了((value < 0) == (value >= 0))
。事实证明,它可以在Visual C ++上正常工作,但在g ++的-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。
在运行时,这只是比较,不需要花费任何时间。它只是更改比较标志配置以检查是否相等。
memcpy
要确保使用g ++而不是有效的强制类型转换,需要通过字节数组使用。在我的第二个答案中对此进行编码。
不依赖于所用NaN的特定IEEE表示的可能解决方案如下:
template<class T>
bool isnan( T f ) {
T _nan = (T)0.0/(T)0.0;
return 0 == memcmp( (void*)&f, (void*)&_nan, sizeof(T) );
}
考虑到(x!= x)不能始终保证为NaN(例如,如果使用-ffast-math选项),我一直在使用:
#define IS_NAN(x) (((x) < 0) == ((x) >= 0))
数字不能同时为<0和> = 0,因此实际上只有在数字既不小于也不大于0时,此检查才通过。基本上根本没有数字或NaN。
如果您愿意,也可以使用此功能:
#define IS_NAN(x) (!((x)<0) && !((x)>=0)
我不确定-ffast-math对它的影响,因此您的里程可能会有所不同。
f != f
也有缺陷。我已经看到llvm可以优化几乎相同的代码。优化器可以传播有关第一个比较的信息,并弄清楚如果第一个比较是第二个比较可能永远都不成立。(如果编译器严格遵守IEEE规则f != f
则要简单得多)
-ffast-math
选项。与Visual C ++一起使用。请参阅(stackoverflow.com/a/42138465/464581)。
对我来说,解决方案可以是一个宏,使其显式内联,从而足够快。它也适用于任何浮点类型。它基于这样一个事实:当值不等于自身时,唯一的情况就是当值不是数字时。
#ifndef isnan
#define isnan(a) (a != a)
#endif
在我看来,最好的真正跨平台方法是使用联合并测试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;
}
union
在两种类型之间进行类型打点的用法可能无法按预期进行(:sad_panda :)。正确的方法(尽管实际上并不太像所希望的那样可移植)将是完全避免double
合并,并从中将memcpy 转换为其他uint64_t
变量,然后使用该辅助变量进行测试。
在x86-64上,您可以使用非常快速的方法来检查NaN和无穷大,无论使用哪种-ffast-math
编译器选项,它们都可以工作。(f != f
,std::isnan
, std::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; }
段的std
版本isinf
并从段中isfinite
加载2个double/float
常量.data
,在最坏的情况下,它们可能导致2个数据高速缓存未命中。上面的版本不加载任何数据,inf_double_shl1
并且inf_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;
}
使用以下事实:ucomisd
如果任何参数为NaN ,则指令会设置奇偶校验标志。std::isnan
当没有-ffast-math
指定选项时,这是如何工作的。
如上面的注释所述,!= 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或%.f格式打印'nan'。因此,此代码检查字符串的第一个字符为“ n”(如“ nan”)
340282346638528859811704183484516925440.000
a = FLT_MAX
。他必须使用char s[7]; sprintf(s, "%.0g", a);
,如果使用则为6 chrs a=-FLT_MAX
,否则-3e+38
通过检查其是否在双精度限制内,这可以检测到无限以及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;
FLT_MIN
,DBL_MIN
并LDBL_MIN
更加仔细。这些被定义为每种类型的最小归一化值。例如,单精度具有超过800万个合法分母值,这些分母值大于零且小于零FLT_MIN
(且不是NaN)。