在 C 中,测试一个浮点数是否为 NaN 与测试两个浮点数是否相等一样快吗?这就是
isnan()
与两个浮点之间的简单相等测试一样快吗?
我特别感兴趣的是在标准现代 Intel/AMD 平台上使用
gcc
。
这是一段 C 代码示例。
#include <math.h>
int main(double x)
{
return isnan(x);
}
在 x64 上使用 GCC,
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
编译为与将浮点数与其自身进行比较相同的东西,请参阅链接的问题了解原因。
我现在看到你实际上拥有了
double
,但这并没有从质上改变任何东西。
问题是我们是否应该在代码中使用 NaN 以外的东西来表示未知值。 (OP评论)
那么你应该将
isnan(x)
与 x == some_constant
进行比较。如果 some_constant
没有值 0 或 NAN,那么如果使用典型的 FP 表示,则比较可能只是位比较 - 在速度上很难击败它。
不过,NaN 更惯用。
不。相等测试是内联的,因此您需要付出调用 isnan() 的函数调用开销的代价。但平等不能在没有 IEEE 的情况下使用,所以......
对于 GCC(也许还有 Clang),优化
isnan()
函数调用的关键是代码中是否存在信号 NaN,以及在遇到信号 NaN 时是否会引发浮点异常。
GCC 默认情况下假定
-fno-signaling-nans
,因此 isnan(x)
可以安全地优化为 (x != x)
,在带有 SSE2 的 x86 上,它将转换为 UCOMISS 或 UCOMISD 指令。
设置
-fsignaling-nans
禁用此类优化。
ISO/IEC TS 18661-1(已纳入即将推出的 C23 标准)澄清了
isnan()
的行为,即该函数永远不会引发任何异常,即使参数是一个信号 NaN 时也是如此。 (讽刺的是,IEEE 754 中的“信号”NaN 并不总是发出信号。)
另一方面,当
(x != x)
为 sNaN 时,x
表达式会抛出 FP_INVALID 异常。
这里是“安静”NaN 和“信号”NaN 的各种表达式的行为的快速比较表:
| 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
(表中“FPE”表示抛出浮点异常; 特别是“无效”例外。)