浮点除零的行为

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会触发硬件异常并使应用程序终止.

  • 语言律师可能会告诉您`std :: numeric_limits <T> :: is_iec559()`是一个实现定义的数量,它反映了符合IEC 559的声明,而不是保证实现正确支持IEEE 754.律师就是这样. (8认同)
  • 我不确定你在第二段中的断言.UB是UB,好的编译器[可能选择不编译代码](http://melpon.org/wandbox/permlink/C4Bq0X7WDsJLjJcI). (4认同)
  • 如果编译器没有充分了解目标执行机器以保证iec559一致性,那么它可能不在声明类型符合iec559的情况下.编译器可以自由地违反标准,但就他们而言,它们不是C++编译器,也不是真正的苏格兰人. (3认同)

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_iec559true,并且它是"通常true",那么行为是明确定义的并产生预期结果.

一个非常安全的赌注是:

static_assert(std::numeric_limits<double>::is_iec559, "Please use IEEE754, you weirdo");
Run Code Online (Sandbox Code Playgroud)

...靠近你的代码.

  • 我无法在C++标准中看到任何证明"except"子句的理由.cppreference是否假设因为这是IEC669所说的? (3认同)
  • 请引用标准文件,而不是随机网站;) (3认同)
  • @BoundaryImposition一旦编译器声称`is_iec559`,就会隐式定义它,因为编译器必须具有符合iec559的`double`s,如果他们声称它.然后该标准定义了什么是"1.0/0.0".标准*确实*将iec559标准作为*选项*引入范围,一旦它"进入范围",编译器必须符合这两个标准.C++标准对`1.0/0.0`的作用没有*直接*限制; iec559标准.只有一种方法可以兼容****.如果你声称`is_iec559`,你必须支持C++标准下的`1.0/0.0`. (2认同)
  • @Yakk假设一个实现使得1.0/0.0评估为+ Inf但损坏随机的其他内存位.乍一看,这与C++标准一致,该标准表明行为未定义,并且与IEC559一致,但没有解决它,但显然不符合预期. (2认同)

dbu*_*ush 8

除以0是未定义的行为.

C++标准(C++ 11)的 5.6节:

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

运算/符的整数和浮点操作数之间没有区别.标准仅规定除非操作数,否则除以零是不确定的.

  • 请注意,在我的情况下,它不是积分除零.我质疑有效性的原因是`%`不适用于C++中的非整数类型.C++不是你知道的Java. (3认同)

Sha*_*our 7

除以零和整数和浮点都是未定义的行为[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 = undefinedgcc没有:

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

看到它为clang而且为gcc.


Nat*_*ica 6

在[expr]/4中我们有

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

强调我的

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