std::isinf不适用于-fast数学.如何检查无穷大

std::isinf does not work with -ffast-math. how to check for infinity

本文关键字:检查 无穷大 何检查 数学 不适用 isinf 适用于 -fast std      更新时间:2023-10-16

示例代码:

#include <iostream>
#include <cmath>
#include <stdint.h>
using namespace std;
static bool my_isnan(double val) {
union { double f; uint64_t x; } u = { val };
return (u.x << 1) > (0x7ff0000000000000u << 1);
}
int main() {
cout << std::isinf(std::log(0.0)) << endl;
cout << std::isnan(std::sqrt(-1.0)) << endl;
cout << my_isnan(std::sqrt(-1.0)) << endl;
cout << __isnan(std::sqrt(-1.0)) << endl;
return 0;
}

在线编译器。

利用-ffast-math;0,0,1,1"——如果没有;1,1,1";。

这是正确的吗?我认为std::isinf/std::isnan在这些情况下仍然应该与-ffast-math一起工作。

此外,如何使用-ffast-math检查无穷大/NaN?您可以看到my_isnan在做这件事,它确实有效,但该解决方案当然非常依赖于体系结构。此外,为什么my_isnan在这里工作,而std::isnan不工作?那么__isnan__isinf呢。他们总是工作吗?

对于-ffast-mathstd::sqrt(-1.0)std::log(0.0)的结果是什么。它是未定义的,还是应该是NaN/-Inf?

相关讨论:(GCC)[Bug libstdc++/50724]新增:是否仅在g++中被finite数学打破,(Mozilla)Bug 416287-isnan 的性能改进机会

注意-ffast-math可能会使编译器忽略/违反IEEE规范,请参阅http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Optimize-Options.html#Optimize-选项:

除了-Ofast之外,任何-O选项都不会打开此选项,因为它可能导致依赖于用于数学函数的IEEE或ISO规则/规范的实现。然而,它可能会为不需要的程序生成更快的代码这些规范的保证。

因此,使用-ffast-math不能保证在应该看到的地方看到无穷大。

特别是,-ffast-math开启-ffinite-math-only,请参阅http://gcc.gnu.org/wiki/FloatingPointMath意思是(从http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Optimize-Options.html#Optimize-选项)

[…]浮点运算优化,假设参数和结果不是NaNs或+-Infs

这意味着,通过启用-ffast-math,您向编译器承诺您的代码永远不会使用无穷大或NaN,这反过来又允许编译器优化代码,例如,用常量false替换对isinfisnan的任何调用(并从中进一步优化)。如果你违背了对编译器的承诺,编译器就不需要创建正确的程序。

因此,答案很简单,如果你的代码可能有无穷大或NaN(这是由你使用isinfisnan的事实强烈暗示的),你就不能启用-ffast-math,否则你可能会得到错误的代码。

my_isnan的实现可以工作(在某些系统上),因为它直接检查浮点数的二进制表示。当然,处理器仍然可能进行(一些)实际计算(取决于编译器所做的优化),因此实际的NaN可能会出现在内存中,您可以检查它们的二进制表示,但如上所述,std::isnan可能已被常量false取代。同样可能发生的情况是,编译器将sqrt替换为甚至不为输入-1生成NaN的某个版本。为了查看编译器进行了哪些优化,请编译到汇编程序并查看代码。

为了进行一个(并非完全无关的)类比,如果你告诉编译器你的代码是用C++编写的,你就不能指望它正确地编译C代码,反之亦然(有一些实际的例子,例如,在C和C++中都有效的代码在用每种语言编译时会产生不同的行为吗?)。

启用-ffast-math并使用my_isnan是个坏主意,因为这会使一切都非常依赖于机器和编译器。你不知道编译器总体上做了什么优化,所以可能还有其他隐藏的问题,因为你使用的是非有限数学,但告诉编译器其他的。

一个简单的修复方法是使用-ffast-math -fno-finite-math-only,它仍然会提供一些优化。

也可能是你的代码看起来像这样:

  1. 过滤掉所有的无穷大和NaN
  2. 对过滤后的值进行一些有限的数学运算(我指的是保证永远不会创建无穷大或NaN的数学运算,这必须经过非常仔细的检查)

在这种情况下,您可以拆分代码,并使用优化#pragma__attribute__为给定的代码段选择性地打开和关闭-ffast-math(分别为-ffinite-math-only-fno-finite-math-only)(然而,我记得与此相关的GCC的某些版本存在一些问题),或者只需将代码拆分为单独的文件,并使用不同的标志进行编译。当然,如果可以隔离可能出现无穷大和NaN的部分,这也适用于更通用的设置。如果您不能隔离这些部分,这强烈表明您不能将-ffinite-math-only用于此代码。

最后,重要的是要理解-ffast-math并不是一个无害的优化,它只是让你的程序更快。它不仅会影响代码的性能,还会影响代码的正确性(除此之外,如果我没记错的话,William Kahan在他的主页上有一系列恐怖故事,请参阅每个程序员都应该知道浮点运算的内容)。简而言之,您可能会得到更快的代码,但也会得到错误或意外的结果(请参阅下面的示例)。因此,只有当你真正知道自己在做什么,并且你已经绝对确定

  1. 优化不会影响特定代码的正确性,或者
  2. 优化引入的错误对代码来说并不重要

根据是否使用此优化,程序代码的行为实际上可能截然不同。特别是,当启用诸如-ffast-math之类的优化时,它可能会表现出错误(或者至少与您的预期非常相反)。以以下程序为例:

#include <iostream>
#include <limits>
int main() {
double d = 1.0;
double max = std::numeric_limits<double>::max();
d /= max;
d *= max;
std::cout << d << std::endl;
return 0;
}

在没有任何优化标志的情况下编译时,将按预期产生输出1,但使用-ffast-math时,它将输出0