浮点除零的行为

时间:2017-03-21 12:10:03

标签: c++ floating-point language-lawyer undefined-behavior divide-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。但我也听到有传言说严格说浮点除零的行为是 undefined ,因此上面的代码不能被认为是可移植的C ++。 (理论上,这会消除我百万行和代码堆栈的完整性。糟糕。)

谁对?

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

7 个答案:

答案 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_iec559true"usually true",则行为定义明确并产生预期结果。

一个非常安全的赌注是打倒一个:

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

...靠近你的代码。

答案 2 :(得分:7)

除以0是未定义的行为

来自C++ standard (C++11)

的第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 undefinedclang 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>

查看live for clanglive for gcc

答案 4 :(得分:6)

在[expr] / 4中我们有

  

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

强调我的

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

答案 5 :(得分:1)

关于提交者的问题'谁是正确的?',可以说两个答案都是正确的。事实上,C标准将行为描述为“未定义”,并未规定底层硬件实际上做了什么;它只是意味着如果你希望你的程序根据标准而有意义 - 你可以不假设 - 硬件实际上实现了那个操作。但是,如果您碰巧在实现IEEE标准的硬件上运行,您会发现该操作实际上已实现,其结果符合IEEE标准。

答案 6 :(得分:0)

这还取决于浮点环境。

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

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

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