浮点除以零的行为
The behaviour of floating point division by zero
>考虑
#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
将非零数除以 ±0.0 得到正确符号的无穷大,并提出
FE_DIVBYZERO
除以 0.0 得到 NaN 并
FE_INVALID
提高
我们在这里谈论的是浮点除法,所以它实际上是实现定义的,double
除以零是否未定义。
如果std::numeric_limits<double>::is_iec559
是true
,并且它是"通常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 环境中可用。在所有这些标准化之前,还有一些特定于平台的变化。
我希望启用异常会使代码运行得更慢,因此可能出于这个原因,我知道的大多数平台默认禁用异常。
- C++将浮点指针值舍入为小数位数
- 如何防止 c++ 在从浮点型转换为双精度型(不适用于 IO)时添加额外的小数?
- 为什么在浮点中从大到小会引入更多的误差
- 我可以信任表示整数的浮点或双精度来保持精度吗
- 为什么mpfr_printf与十六进制浮点(%a转换说明符)的printf不同
- C++Lua用户数据扰乱了Mathfu浮点值
- 将CHW格式的浮点向量转换为cv::Mat
- 算术运算的结果类似于:C浮点变量中的1/3
- 格式化浮点值:返回默认值
- 浮点定向舍入和优化
- 短是浮点类型吗?
- 从VS 2015更新3更新到VS2015更新3 d后浮点计算行为不同的原因
- 使用 int 表示浮点除法 C++
- 像union_这样的 Boost.Geometry 操作如何处理浮点类型的基本不精确性?
- 浮点异常(核心转储)#694457
- 奇怪的缩小转换在 g++ 编译器中加倍到浮点警告
- C++17 十六进制浮点文字单精度后缀冲突?
- 如何在 8.th 小数之后擦除/不显示浮点小数
- 如何(安全地)除最大值,得到浮点值?
- 浮点,是一个足以防止被零除的相等比较