考虑
#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。但我也听到有传言说严格说浮点除零的行为是 undefined ,因此上面的代码不能被认为是可移植的C ++。 (理论上,这会消除我百万行和代码堆栈的完整性。糟糕。)
谁对?
注意我对实施定义感到满意,但我在这里谈论吃猫,打喷嚏的未定义的行为。
答案 0 :(得分:38)
C ++标准并未强制实施IEEE 754标准,因为这主要取决于硬件架构。
如果硬件/编译器正确实现了IEEE 754标准,则该部门将提供预期的INF,-INF和NaN,否则......它取决于。
未定义意味着,编译器实现决定,并且有许多变量,如硬件架构,代码生成效率,编译器开发人员懒惰等。
<强>来源:强>
C ++标准规定除以0.0为undefined
C ++标准5.6.4
...如果/或%的第二个操作数为零,则行为未定义
C ++标准18.3.2.4
... static constexpr bool is_iec559;
... 56。当且仅当类型符合IEC 559标准时才为真.217
... 57。对所有浮点类型都有意义。
IEEE754的C ++检测:
标准库包含一个模板,用于检测是否支持IEEE754:
static constexpr bool is_iec559;
#include <numeric>
bool isFloatIeee754 = std::numeric_limits<float>::is_iec559();
如果不支持IEEE754怎么办?
取决于,通常除以0会触发硬件异常并使应用程序终止。
答案 1 :(得分: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
且"usually true
",则行为定义明确并产生预期结果。
一个非常安全的赌注是打倒一个:
static_assert(std::numeric_limits<double>::is_iec559, "Please use IEEE754, you weirdo");
...靠近你的代码。
答案 2 :(得分:7)
除以0是未定义的行为。
的第5.6节二进制
/
运算符产生商和二进制%
运算符 从第一个表达式的除法得到余数 第二。 如果/
或%
的第二个操作数为零,则行为为 undefined。对于整数操作数,/
运算符产生代数 丢弃任何小数部分的商;如果商a/b
是 可以在结果类型中表示,(a/b)*b + a%b
等于a
。
/
运算符的整数和浮点操作数之间没有区别。除了操作数之外,标准仅规定除以零是不确定的。
答案 3 :(得分:7)
除以零的整数和浮点都是未定义的行为[expr.mul]p4:
二进制/运算符得出商,二进制%运算符得出除法的余数 第一个表达式的第二个。 如果/或%的第二个操作数为零,则行为未定义。 ...
尽管实现可以选择支持Annex F,该语义具有明确定义的语义,用于将浮点数除以零。
从此clang错误报告clang sanitizer regards IEC 60559 floating-point division by zero as undefined中我们可以看到,即使定义了宏 __ STDC_IEC_559 __ ,它仍由系统标头定义,至少对于clang不支持 Annex F ,因此对clang来说仍然是未定义的行为:
C标准的附件F(IEC 60559 / IEEE 754支持)定义了 浮点数除以零,但为clang(3.3和3.4 Debian快照) 认为它是未定义的。这是不正确的:
对附件F的支持是可选的,我们不支持。
#if STDC_IEC_559
此宏是由您的系统标题而不是我们定义的;这是 系统标题中的错误。 (FWIW,海湾合作委员会不完全支持附件 还是IIRC,所以它甚至不是Clang特有的错误。)
该错误报告和另外两个错误报告UBSan: Floating point division by zero is not undefined和clang should support Annex F of ISO C (IEC 60559 / IEEE 754)表示gcc在浮点除以零方面符合附件F 。
尽管我同意无条件定义 STDC_IEC_559 并非由C库决定,但问题只针对clang。 GCC并不完全支持附件F,但是至少其意图是默认支持它,并且如果不改变舍入模式,则可以很好地定义部门。如今不支持IEEE 754(至少像除零这样的基本特征)被视为不良行为。
gcc Semantics of Floating Point Math in GCC wiki提供了进一步的支持,它表明-fno-signaling-nans是默认设置,它与gcc optimizations options documentation一致:
默认值为-fno-signaling-nans。
有趣的是,UBSan的clang默认在gcc does not的 -fsanitize = undefined 下包含 float-divide-by-zero 。
检测浮点除以零。与其他类似选项不同,
不会启用 -fsanitize = float-divide-by-zero零-fsanitize = undefined,因为将浮点数除以零可能是获得无穷大和NaN的合法方法。 < / p>
答案 4 :(得分:6)
在[expr] / 4中我们有
如果在评估表达式期间,结果未在数学上定义或未在其类型的可表示值范围内,行为未定义。 [注意:大多数现有的C ++实现忽略整数溢出。除零处理,使用零除数形成余数,所有浮点异常因机器而异,通常可通过库函数调整。 - 后注]
强调我的
因此,根据标准,这是未定义的行为。它继续说这些案例中的一些实际上是由实现处理的并且是可配置的。所以它不会说它是实现定义的,但它确实让你知道实现确实定义了一些这种行为。
答案 5 :(得分:1)
关于提交者的问题'谁是正确的?',可以说两个答案都是正确的。事实上,C标准将行为描述为“未定义”,并未规定底层硬件实际上做了什么;它只是意味着如果你希望你的程序根据标准而有意义 - 你可以不假设 - 硬件实际上实现了那个操作。但是,如果您碰巧在实现IEEE标准的硬件上运行,您会发现该操作实际上已实现,其结果符合IEEE标准。
答案 6 :(得分:0)
这还取决于浮点环境。
cppreference有详细信息: http://en.cppreference.com/w/cpp/numeric/fenv (虽然没有例子)。
这应该适用于大多数桌面/服务器C ++ 11和C99环境。还存在特定于平台的变体,这些变体早于所有这些的标准化。
我希望启用异常会使代码运行得更慢,因此可能出于这个原因,我知道大多数平台默认禁用异常。