在某些情况下,使用 GCC + 编译时,将正常的浮点数与std::numeric_limits<float>::quiet_NaN()总是产生比较(在 Linux 和 MinGW 上测试)。在其他情况下,它总是会产生(这将是符合 IEEE 的行为)。"true""-ffast-math""false"
如果quiet_NaN在编译时已知其中一个值是 a ,则该行为符合 IEEE 标准,并且分支会被优化掉。
我知道这"-ffast-math"允许违反 IEEE 规则,并且它意味着"-ffinite-math-only"假设没有 NaN。但即使"-ffast-math"结果quiet_NaN()具有特定的位模式(例如 7FF80000),那么与普通浮点数的比较如何0.5f可能产生真值?
这是显示不同情况的代码示例。请用"g++ -O3 -ffast-math".
#include <iostream>
#include <limits>
int main() {
#if 1
// make sure that the value of 'x' is not known at compile time
bool isnan;
std::cout << "use nan (0|1): ";
std::cin >> isnan;
#else
// otherwise GCC correctly applies IEEE rules for NaN and the branch below is optimized away accordingly
bool isnan = true;
#endif
float x = isnan ? std::numeric_limits<float>::quiet_NaN() : 0.5f;
std::cout << "x: " << x << std::endl;
float a;
std::cout << "type a float: ";
std::cin >> a;
#if 1
// *always* prints 1 (!)
std::cout << a << " equal to " << x << ": " << (x == a) << std::endl;
#else
// always prints false - the opposite from above!
std::cout << a << " equal to " << x << ": " << ((x == a) ? "true" : "false") << std::endl;
#endif
return 0;
}
Run Code Online (Sandbox Code Playgroud)
另外,这里有一个 Godbolt链接。我猜相关部分在第 66 行(带有"-ffast-math")。67(没有"-ffast-math")。有人可以向我解释这些说明之间的区别吗?
GCC 的这种行为是否可以接受还是我应该提交错误报告?
编辑:我想说明我不需要知道一个特定的数字是否是 NaN(我知道这不是用“-ffast-math”指定的),我只对两个数字是否(不是)感兴趣平等的。在我的实际代码中,我有一个浮点值缓存,并且仅当输入与缓存值不同时才执行更新操作。我已经用相当多的 NaN 初始化了缓存,所以它们不会与任何普通浮点数相等,并且保证第一个输入会导致更新。这工作正常,但是一旦我添加了“-ffast-math”,检查newval != oldval总是返回false,所以永远不会有更新。我在 SuperCollider 源代码中看到了这个 quiet_NaN 模式,并发现它非常优雅。
gcc 文档说:
-ffast-math设置的选项
-fno-math-errno,-funsafe-math-optimizations,-ffinite-math-only,-fno-rounding-math,-fno-signaling-nans,-fcx-limited-range和-fexcess-precision=fast。此选项导致
__FAST_MATH__定义预处理器宏。此选项不会被任何
-O选项打开,-Ofast因为它可能导致依赖于数学函数的 IEEE 或 ISO 规则/规范的精确实现的程序的错误输出。然而,对于不需要这些规范保证的程序,它可能会产生更快的代码。
此外,-ffinite-math-only(包含在 中-ffast-math)导致std::isnan、std::isinf以及x != x始终返回false、std::isfinite始终返回true。
https://www.felixcloutier.com/x86/ucomisd
该
COMISD指令与该指令的不同之处在于,UCOMISD当源操作数是 QNaN 或 SNaN 时,它会发出 SIMD 浮点无效操作异常 (#I) 信号。UCOMISD仅当源操作数是 SNaN 时,该指令才发出无效数字异常信号。
-ffinite-math-only导致编译器发出COMISD而不是UCOMISD并省略检查比较结果NaN
生成的代码是:
comiss xmm2, DWORD PTR [rsp+28]
sete sil
Run Code Online (Sandbox Code Playgroud)
与NaNset ZF、PF、CFflags进行比较,导致在与 比较时sete sil产生true值NaN,但没有NaN预期与-ffinite-math-only,所以这就是未定义行为在这里表现出来的方式。
NaN预计何时发出的程序集检查PF标志,该标志在至少一个操作数为NaN:
ucomiss xmm1, DWORD PTR [rsp+28]
mov eax, 0
setnp sil # <---- condition on NaN
cmovne esi, eax
Run Code Online (Sandbox Code Playgroud)
与-ffast-math您可能喜欢使用这些:检查是否一个双(或浮动)为NaN在C ++中