在C语言中,测试一个浮点数是否为NaN和测试两个浮点数是否相等一样快吗?也就是说,isnan()和两个浮点数之间的简单相等性测试一样快吗?我特别感兴趣的是在标准的现代英特尔/AMD平台上使用gcc。下面是一段C代码的示例。
isnan()
gcc
#include <math.h> int main(double x) { return isnan(x); }
字符串
mftmpeh81#
在x64上使用GCC,math.h的isnan(float)编译为
math.h
isnan(float)
jmp __isnanf
字符串使用尾部调用优化,但有效地调用函数。被调用的函数将不得不做一些等效于代码的事情,至少我没有看到任何更快的方法来实现它。这留下了一个问题,它如何比较一个比较没有回答。但这并没有说明“测试浮点数是否为NaN”有多快,因为并不只有一种方法可以做到这一点。最直接的方法,
int isnan2(float x) { return x != x; }
型从字面上看,这与在C级别比较浮点数是一样的。但GCC使之:
xor eax, eax ucomiss xmm0, xmm0 setp al ret
型这和比较两个浮点数不太一样,但很接近,事实上更快一点。测试相等意味着测试无序的情况,就像这里一样,但是还必须测试z标志,像这样(再次来自gcc)
xor eax, eax mov edx, 1 ucomiss xmm0, xmm1 setp al cmovne eax, edx ret
型额外的好处:使用<cmath>使isnan编译成与比较浮点数本身相同的东西,请参阅链接的问题了解原因。Godbolt link for convenience我现在看到你实际上有double,但这并没有改变任何性质。
<cmath>
isnan
double
aiazj4mn2#
对于GCC(可能还有Clang),优化isnan()函数调用的关键在于代码中是否存在NaN信号,以及在遇到NaN信号时是否会引发浮点异常。GCC默认采用-fno-signaling-nans,因此isnan(x)可以安全地优化为(x != x),在使用SSE 2的x86上,它将转换为UCOMISS或UCOMISD指令。设置-fsignaling-nans将禁用此类优化。ISO/IEC TS 18661-1(已纳入即将推出的C23标准)阐明了isnan()的行为,即即使参数是一个信令NaN,该函数也不会抛出任何异常(这使得IEEE 754中的“信令”NaN并不总是信令)。另一方面,当x是一个信号NaN时,(x != x)表达式 * 确实 * 抛出FE_INVALID异常(也称为“无效操作”)。下面是一个快速比较表,列出了使用“quiet”NaN和“signaling”NaN的各种表达式的行为:
-fno-signaling-nans
isnan(x)
(x != x)
-fsignaling-nans
x
| isnan(x) | (x != x) | (x >= x) | isgreaterequal(x,x) --------------+----------+-----------+------------+-------------------- -Wfloat-equal | no warn | warn | no warn | no warn --------------+----------+-----------+------------+-------------------- Finite number | false | false | true | true Infinity | false | false | true | true NaN (quiet) | true | true | FPE; false | false SNaN | true | FPE; true | FPE; false | FPE; false
字符串备注:
isgreaterequal()
int
bool
0
如果您的应用程序代码根本不使用信令NaN(或者您只在调试时使用它们),那么您可以为“更便宜”的isnan定义一个宏,它不会发出对libm isnan()函数的调用:
#include <math.h> #ifdef isgreaterequal #define isnan_cheap(x) (!isgreaterequal(x, x)) #endif
型由于isgreaterequal宏在C中是泛型类型,因此此自定义宏也是泛型类型。
isgreaterequal
p8ekf7hl3#
问题是我们是否应该在代码中使用NaN以外的东西来表示未知值。然后你应该比较isnan(x)和x == some_constant。如果some_constant没有0或NAN的值,那么如果使用典型的FP表示,比较可以简单地进行位比较-很难在速度上击败它。不过,NaN更地道。
x == some_constant
some_constant
ecbunoof4#
不。等式测试是内联的,所以你要付出调用isnan()的函数调用开销的代价。但是等式不能在不使用IEEE的情况下使用,所以...
4条答案
按热度按时间mftmpeh81#
在x64上使用GCC,
math.h
的isnan(float)
编译为字符串
使用尾部调用优化,但有效地调用函数。被调用的函数将不得不做一些等效于代码的事情,至少我没有看到任何更快的方法来实现它。这留下了一个问题,它如何比较一个比较没有回答。
但这并没有说明“测试浮点数是否为NaN”有多快,因为并不只有一种方法可以做到这一点。最直接的方法,
型
从字面上看,这与在C级别比较浮点数是一样的。但GCC使之:
型
这和比较两个浮点数不太一样,但很接近,事实上更快一点。测试相等意味着测试无序的情况,就像这里一样,但是还必须测试z标志,像这样(再次来自gcc)
型
额外的好处:使用
<cmath>
使isnan
编译成与比较浮点数本身相同的东西,请参阅链接的问题了解原因。Godbolt link for convenience
我现在看到你实际上有
double
,但这并没有改变任何性质。aiazj4mn2#
对于GCC(可能还有Clang),优化
isnan()
函数调用的关键在于代码中是否存在NaN信号,以及在遇到NaN信号时是否会引发浮点异常。GCC默认采用
-fno-signaling-nans
,因此isnan(x)
可以安全地优化为(x != x)
,在使用SSE 2的x86上,它将转换为UCOMISS或UCOMISD指令。设置
-fsignaling-nans
将禁用此类优化。ISO/IEC TS 18661-1(已纳入即将推出的C23标准)阐明了
isnan()
的行为,即即使参数是一个信令NaN,该函数也不会抛出任何异常(这使得IEEE 754中的“信令”NaN并不总是信令)。另一方面,当
x
是一个信号NaN时,(x != x)
表达式 * 确实 * 抛出FE_INVALID异常(也称为“无效操作”)。下面是一个快速比较表,列出了使用“quiet”NaN和“signaling”NaN的各种表达式的行为:
字符串
备注:
isnan()
和isgreaterequal()
在技术上返回int
类型的值,而不是bool
。表中的“true”和“false”分别对应于非零值和0
值。如果您的应用程序代码根本不使用信令NaN(或者您只在调试时使用它们),那么您可以为“更便宜”的
isnan
定义一个宏,它不会发出对libmisnan()
函数的调用:型
由于
isgreaterequal
宏在C中是泛型类型,因此此自定义宏也是泛型类型。p8ekf7hl3#
问题是我们是否应该在代码中使用NaN以外的东西来表示未知值。
然后你应该比较
isnan(x)
和x == some_constant
。如果some_constant
没有0或NAN的值,那么如果使用典型的FP表示,比较可以简单地进行位比较-很难在速度上击败它。不过,NaN更地道。
ecbunoof4#
不。等式测试是内联的,所以你要付出调用isnan()的函数调用开销的代价。但是等式不能在不使用IEEE的情况下使用,所以...