浮点除以零的行为

The behaviour of floating point division by zero

本文关键字:浮点除      更新时间:2023-10-16

>考虑

#include <iostream>
int main()
{
double a = 1.0 / 0;
double b = -1.0 / 0;
double c = 0.0 / 0;
std::cout << a << b << c; // to stop compilers from optimising out the code.    
}

我一直认为a将是+Inf,b将是-Inf,c将是NaN。但我也听到传言说,严格来说,浮点除以零的行为是未定义的,因此上面的代码不能被认为是可移植C++。(从理论上讲,这消除了我的百万行加代码堆栈的完整性。哎呀。

谁是对的?

注意 我对定义的实现感到满意,但我在这里谈论的是吃猫、打喷嚏的未定义行为

C++标准不会强制使用IEEE 754标准,因为这主要取决于硬件架构。

如果硬件/编译器正确实现了 IEEE 754 标准,则部门将提供预期的 INF、-INF 和 NaN,否则......这要看情况。

未定义意味着,编译器实现决定,并且有许多变量,例如硬件体系结构,代码生成效率,编译器开发人员的懒惰性等。

源:

C++标准规定除以 0.0 是undefined

C++ 标准 5.6.4

。如果/或 % 的第二个操作数为零,则行为未定义

C++ 标准 18.3.2.4

。静态 constexpr 布尔is_iec559;

。56. 当且仅当类型符合 IEC 559 标准时为 true.217

。57. 对所有浮点类型都有意义。

C++检测IEEE754:

标准库包含一个模板,用于检测IEEE754是否受支持:

静态 constexpr 布尔is_iec559;

#include <numeric>
bool isFloatIeee754 = std::numeric_limits<float>::is_iec559();

如果不支持IEEE754怎么办?

这取决于,通常除以 0 会触发硬件异常并使应用程序终止。

引用 cpp首选项:

如果第二个操作数为零,则行为未定义,除非发生浮点除法并且类型支持 IEEE 浮点运算(参见std::numeric_limits::is_iec559),则:

  • 如果一个操作数是 NaN,则结果为 NaN

  • 将非零数除以 ±0.0 得到正确符号的无穷大,并提出FE_DIVBYZERO

  • 将 0.0
  • 除以 0.0 得到 NaN 并FE_INVALID提高

我们在这里谈论的是浮点除法,所以它实际上是实现定义的,double除以零是否未定义。

如果std::numeric_limits<double>::is_iec559true,并且它是"通常true",那么行为是明确定义的,并产生预期的结果。

一个相当安全的赌注是扑通一声:

static_assert(std::numeric_limits<double>::is_iec559, "Please use IEEE754, you weirdo");

。在您的代码附近。

除以零 整数和浮点都是未定义的行为 [expr.mul]p4:

二进制/运算符产生商,二进制 % 运算符产生除法的余数 第一个表达式由第二个表达式。如果/或 % 的第二个操作数为零,则行为未定义。...

尽管实现可以选择支持附件 F,该附件具有明确定义的用于浮点除以零的语义。

我们可以从这个 clang 错误报告中看到,clang 清理器将 IEC 60559 浮点除以零视为未定义,即使定义了宏__STDC_IEC_559__,它也是由系统标头定义的,至少对于 clang 不支持附录 F,因此对于 clang 仍然是未定义的行为:

C 标准的附录 F(IEC 60559/IEEE 754 支持)定义了 浮点除以零,但叮当(3.3 和 3.4 Debian 快照) 将其视为未定义。这是不正确的:

对附件F的支持是可选的,我们不支持。

#ifSTDC_IEC_559

此宏由您的系统标头定义,而不是由我们定义;这是 系统标头中的错误。(FWIW,海湾合作委员会不完全支持附件 F,IIRC,所以它甚至不是一个特定于Clang的错误。

该错误报告和另外两个错误报告 UBSan:浮点除以零不是未定义的,clang应支持ISO C的附录F(IEC 60559/IEEE 754)表明GCC在浮点除以零方面符合附录F

虽然我同意 C 库不能无条件地定义STDC_IEC_559,但问题是特定于 clang。GCC 并不完全支持附件 F,但至少它的意图是默认支持它,并且如果不更改舍入模式,则分区已明确定义。如今,不支持IEEE 754(至少是处理除以零等基本功能)被认为是不良行为。

这是GCC wiki中浮点数学的gcc语义的进一步支持,它表明-fno-signaling-nans是默认值,它与gcc优化选项文档一致,该文档说:

默认值为 -fno-signaling-nans。

有趣的是,UBSan for clang 默认在-fsanitize=undefined下包含浮点除以零,而 gcc 则没有:

检测浮点除以零。与其他类似选项不同,-fsanitize=float-divide-by-zero 不是由 -fsanitize=undefined 启用的,因为浮点除以零可能是获得无穷大和 NaN 的合法方法。

看到它为叮当而活,为海湾合作委员会而活。

除以0 是未定义的行为

从C++标准(C++11)的第5.6节中:

二元/运算符产生商,二元%运算符 产生第一个表达式除以 第二。如果/%的第二个操作数为零,则行为为 定义。对于积分操作数,/运算符产生代数 丢弃任何小数部分的商;如果商a/b为 在结果类型中可表示,(a/b)*b + a%b等于a

对于/运算符,整数操作数和浮点操作数之间没有区别。 该标准仅指出除以零是不定义的,而不考虑操作数。

在 [expr]/4 中我们有

如果在表达式计算期间,结果未在数学上定义或不在其类型的可表示值范围内,则行为未定义。[ 注意:大多数现有的C++实现都忽略整数溢出。处理除以零,使用零除数形成余数,所有浮点异常因机器而异,通常由库函数调节。—尾注 ]

强调我的

因此,根据标准,这是未定义的行为。 它确实继续说其中一些情况实际上是由实现处理的并且是可配置的。 因此,它不会说它是实现定义的,但它确实让你知道实现确实定义了其中一些行为。

至于提交者的问题"谁是对的?",完全可以说两个答案都是正确的。 C 标准将行为描述为"未定义"这一事实并不规定底层硬件的实际作用;它仅仅意味着,如果您希望程序根据标准有意义,则您(可能不会假设)硬件实际上实现了该操作。 但是,如果您碰巧在实现IEEE标准的硬件上运行,您会发现该操作实际上是实现的,其结果与IEEE标准规定相同。

这也取决于浮点环境。

CPP首选项包含详细信息: http://en.cppreference.com/w/cpp/numeric/fenv (虽然没有例子)。

这应该在大多数桌面/服务器 C++11 和 C99 环境中可用。在所有这些标准化之前,还有一些特定于平台的变化。

我希望启用异常会使代码运行得更慢,因此可能出于这个原因,我知道的大多数平台默认禁用异常。