Bat*_*eba 48 c++ floating-point divide-by-zero undefined-behavior language-lawyer
考虑
#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.
}
Run Code Online (Sandbox Code Playgroud)
我一直认为这a
将是+ Inf,b
将是-Inf,并且c
将是NaN.但我也听到传言说严格来说浮点除零的行为是未定义的,因此上面的代码不能被认为是可移植的C++.(理论上,这会消除我的百万行加上代码堆栈的完整性.糟糕.)
谁是对的?
注意我对实现定义感到满意,但我在谈论吃猫,在这里恶魔打喷嚏的未定义行为.
Adr*_*ire 40
C++标准不强制IEEE 754标准,因为这主要取决于硬件架构.
如果硬件/编译器正确地实现了IEEE 754标准,则除法将提供预期的INF,-INF和NaN,否则......它取决于.
未定义意味着,编译器实现决定,并且有许多变量,如硬件架构,代码生成效率,编译器开发人员懒惰等.
资源:
C++标准规定除以0.0是 undefined
C++标准5.6.4
...如果/或%的第二个操作数为零,则行为未定义
C++标准18.3.2.4
...静态constexpr bool is_iec559;
... 56.当且仅当类型符合IEC 559标准时才为真
... 57.对所有浮点类型都有意义.
IEEE754的C++检测:
标准库包含一个模板,用于检测是否支持IEEE754:
static constexpr bool is_iec559;
#include <numeric>
bool isFloatIeee754 = std::numeric_limits<float>::is_iec559();
Run Code Online (Sandbox Code Playgroud)
如果不支持IEEE754怎么办?
它取决于,通常除以0会触发硬件异常并使应用程序终止.
Que*_*tin 23
引用cppreference:
如果第二个操作数为零,则行为是未定义的,除非发生浮点除法且类型支持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_iec559
是true
,并且它是"通常true
",那么行为是明确定义的并产生预期结果.
一个非常安全的赌注是:
static_assert(std::numeric_limits<double>::is_iec559, "Please use IEEE754, you weirdo");
Run Code Online (Sandbox Code Playgroud)
...靠近你的代码.
除以0是未定义的行为.
从C++标准(C++ 11)的 5.6节:
二元
/
运算符产生商,二元%
运算符从第一个表达式除以第二个表达式得到余数.如果第二个操作数为/
或%
为零,则行为未定义.对于积分操作数,/
运算符产生代数商,丢弃任何小数部分; 如果商a/b
在结果类型中可表示,(a/b)*b + a%b
则等于a
.
运算/
符的整数和浮点操作数之间没有区别.标准仅规定除非操作数,否则除以零是不确定的.
除以零和整数和浮点都是未定义的行为[expr.mul] p4:
二元/运算符产生商,二元%运算符从第一个表达式除以第二个表达式得到余数.如果/或%的第二个操作数为零,则行为未定义....
虽然实现可以选择性地支持附件F,其具有用于浮点除法的良好定义的语义为零.
我们可以从这个clang bug报告中看到clang sanitizer将IEC 60559的浮点除法视为未定义,即使定义了宏__STDC_IEC_559__,它也是由系统头定义的,至少对于clang不支持附件F等等对于clang仍然是未定义的行为:
C标准的附录F(IEC 60559/IEEE 754支持)将浮点除法定义为零,但clang(3.3和3.4 Debian快照)将其视为未定义.这是不正确的:
附件F的支持是可选的,我们不支持.
#if STDC_IEC_559
此宏由您的系统标头定义,而不是由我们定义; 这是系统标头中的错误.(FWIW,GCC也不完全支持附件F,IIRC,因此它甚至不是特定于Clang的错误.)
该错误报告和另外两个错误报告UBSan:浮点除零并非未定义且clang应支持ISO C的附录F(IEC 60559/IEEE 754)表明gcc符合附件F关于浮点除以零.
虽然我同意无法无条件地定义STDC_IEC_559的C库,但问题是clang特有的.海湾合作委员会并不完全支持附件F,但至少它的意图是默认支持它,如果不改变舍入模式,则划分得很好.如今不支持IEEE 754(至少基本功能,如处理除零)被视为不良行为.
这是GCC wiki中浮点数学的gcc 语义的进一步支持,它表明-fno-signaling-nans是默认的,与gcc优化选项文档一致,它说:
默认值为-fno-signaling-nans.
有趣的是,UBSan for clang默认包含float-divide-by-zero under -fsanitize = undefined而gcc没有:
检测浮点除以零.与其他类似选项不同,-fsanitize = float-divide-by-zero不会被-fsanitize = undefined启用,因为浮点除零可能是获得无穷大和NaN的合法方式.
在[expr]/4中我们有
如果在评估表达式期间,结果未在数学上定义或未在其类型的可表示值范围内,则行为未定义.[注意:大多数现有的C++实现忽略整数溢出.除零处理,使用零除数形成余数,所有浮点异常因机器而异,通常可通过库函数调整. - 尾注]
强调我的
因此,根据标准,这是未定义的行为.它继续说这些案例中的一些实际上是由实现处理的并且是可配置的.所以它不会说它是实现定义的,但它确实让你知道实现确实定义了一些这样的行为.