C ++警告:双零除数


98

情况1:

#include <iostream>

int main()
{
    double d = 15.50;
    std::cout<<(d/0.0)<<std::endl;
}

它在编译时没有任何警告和打印inf。好的,C ++可以处理零除(实时查看)。

但,

情况2:

#include <iostream>

int main()
{
    double d = 15.50;
    std::cout<<(d/0)<<std::endl;
}

编译器发出以下警告(实时查看):

warning: division by zero [-Wdiv-by-zero]
     std::cout<<(d/0)<<std::endl;

为什么在第二种情况下编译器会发出警告?

0 != 0.0

编辑:

#include <iostream>

int main()
{
    if(0 == 0.0)
        std::cout<<"Same"<<std::endl;
    else
        std::cout<<"Not same"<<std::endl;
}

输出:

Same

9
我假设在第二种情况下它采用零作为整数并发出警告,即使以后将使用double来完成计算(我认为这是d为double时的行为)。
Qubit

10
确实,这是一个QoI问题。警告或缺少警告都不是C ++标准本身所要求的。您在使用GCC吗?
StoryTeller-Unslander Monica '18


5
@StoryTeller什么是QoI?en.wikipedia.org/wiki/QoI吗?
user202729

5
关于最后一个问题,“ 0是否等于0.0?” 答案是是相同的,但是正如您所发现的,这并不意味着它们是相同的。不同种类!就像“A”不等同于65
李斯特先生

Answers:


108

IEEE很好地定义了浮点除以零并给出无穷大(根据分子的值是正数还是负数NaN对于±0))。

对于整数,无法表示无穷大,并且语言将操作定义为具有未定义的行为,因此编译器将帮助您引导您从该路径中清除。

但是,在这种情况下,由于分子为a double,除数(0)也应提升为双精度数,因此没有理由在不给出警告的情况下发出警告,0.0因此我认为这是编译器错误。


8
两者都是浮点除法。在中d/00转换为的类型d

43
请注意,C ++不需要使用IEEE 754(即使我从未见过使用其他标准的编译器)
。– Yksisarvinen

1
@hvd,好的一点,就是这种情况看起来像是编译器错误
Motti

14
我同意在这两种情况下均应发出警告,或者在两种情况下均不发出警告(取决于编译器对浮零除法的处理方式)
MM

8
可以肯定的是,除以零的浮点数也是UB-只是GCC根据IEEE 754来实现它。尽管不必这样做。
马丁·邦纳

42

在Standard C ++中,这两种情况都是undefined behavior。任何事情都可能发生,包括格式化硬盘。您不应该期望或依赖“确定返回价”或任何其他行为。

显然,编译器决定在一种情况下发出警告,而在另一种情况下发出警告,但这并不意味着一个代码可以,而一个代码则不能。这只是编译器生成警告的一个怪癖。

根据C ++ 17标准[expr.mul] / 4:

二元/运算符得出商,二元%运算符得出第一个表达式除以第二个表达式的余数。如果/或的第二个操作数%为零,则行为不确定。


21
并非如此,在浮点算术除以零的定义很明确。
Motti '18

9
@Motti-如果仅将自己限制为C ++标准,则没有这样的保证。坦率地说,这个问题的范围没有明确说明。
StoryTeller-Unslander Monica '18

9
@StoryTeller我很确定(尽管我没有为此查看标准文档本身),如果std::numeric_limits<T>::is_iec559true,则除以零T不是UB(在大多数平台上,它是true用于double和的float,尽管为了便于携带,您会需要使用if或来明确检查if constexpr
Daniel H

6
@DanielH-“合理”实际上是很主观的。如果这个问题被标记为语言律师,那么将会有另外一组(小得多的)合理假设。
StoryTeller-Unslander Monica '18

5
@MM请记住,这正是您所说的,但仅此而已:undefined并不意味着没有强加任何要求,这意味着标准没有强加任何要求。我认为在这种情况下,实现定义is_iec559为的事实true意味着该实现正在记录标准未定义的行为。只是在这种情况下,可以以编程方式读取实现的文档。甚至不是唯一一个:有is_modulo符号整数类型也是如此。

12

回答我最好的猜测此问题的讨论将是编译器发出警告之前的进行转换intdouble

因此,步骤将如下所示:

  1. 解析表达式
  2. 算术运算符 /(T, T2),其中T=doubleT2=int
  3. 检查std::is_integral<T2>::valuetrueb == 0-这会触发报警。
  4. 发出警告
  5. 执行的隐式转换T2double
  6. 执行定义明确的划分(因为编译器决定使用IEEE 754)。

这当然是推测,并且基于编译器定义的规范。从标准的角度来看,我们正在处理可能的不确定行为。


请注意,根据GCC文档,这是预期的行为
(顺便说一句,该标志似乎无法在GCC 8.1中明确使用)

-Wdiv-by-zero
警告有关编译时整数被零除的警告。这是默认值。要禁止显示警告消息,请使用-Wno-div-by-zero。没有警告将浮点除以零,因为这可能是获得无穷大和NaN的合法方法。


2
这不是C ++编译器的工作方式。编译器必须执行重载解析/才能知道它的划分。如果左侧将是一个Foo对象,并且会有一个operator/(Foo, int),则它甚至可能不是被分割的。编译器仅在built-in / (double, double)使用右侧的隐式转换进行选择时才知道其除法。但这意味着它不进行除法int(0),而是进行除法double(0)
MSalters

@MSalters 请参阅此。我对C ++的了解是有限的,但根据参考资料operator /(double, int)当然可以接受。然后,它说转换是在执行任何其他操作之前执行的,但是GCC可以快速检查是否T2为整数类型,如果为整数则b == 0发出警告。不确定是否完全符合标准,但是编译器在定义警告以及何时触发它们方面具有完全的自由。
Yksisarvinen

2
我们在这里谈论内置运算符。那真是有趣。它实际上不是功能,因此您不能使用其地址。因此,您无法确定是否operator/(double,int)确实存在。例如,编译器可以决定通过用代替a/b常量来优化常量。当然,这意味着您不再在运行时调用,而是更快地调用。但是,现在正是优化程序跳闸了,它必须提供给它的常量ba * (1/b)operator/(double,double)operator*(double,double)1/0operator*
MSalters

@MSalters通常浮点除法不能与乘法可能是由特殊的情况下分开进行更换,如2
user202729

2
@ user202729:即使对于整数除法,GCC也会这样做。让它陷入片刻。GCC用整数乘法代替整数除法。是的,这是可能的,因为GCC知道它在环上运行(数字以2 ^ N为模)
MSalters '18

9

在这个答案中,我将不涉及UB / UB崩溃。

我只是想指出这一点0,并0.0 有不同的,尽管0 == 0.0评估为true。0是一个int文字,0.0也是一个double文字。

但是,在这种情况下,最终结果是相同的:d/0是浮点除法,因为它d是double,因此0隐式转换为double。


5
我看不出,这是相关的,因为通常的算术转换指定划分double通过一个int装置,该int被转换成double ,并且它是在标准中指定的是0转换到0.0 (conv.fpint / 2)
MM

OP想要知道的@MM是否00.0
bolov

2
问题是“是0 != 0.0?”。OP从不询问它们是否“相同”。在我看来,这个问题的目的还在于d/0行为是否与d/0.0
MM

2
@MM- OP确实询问了。通过这些常量编辑,他们并没有真正表现出体面的SO网络礼节。
StoryTeller-Unslander Monica,

7

我会说 foo/0并且foo/0.0一样的。即,第一个(整数除法或浮点除法)的结果效果高度依赖的类型foo,而第二个(第二个结果将始终是浮点除法)则并非如此。

两者是否为UB无关紧要。引用标准:

允许的不确定行为范围包括完全忽略情况并产生不可预测的结果, 到以环境特征的记录方式在翻译或程序执行期间的行为(带有或不带有诊断消息的行为)到终止翻译或执行(带有发布的行为)诊断消息)。

(强调我的)

考虑“将分配的括号用作真值 ”警告:告诉编译器您确实要使用分配结果的方法是通过显式,并在分配周围添加括号。结果语句具有相同的效果,但是它告诉编译器您知道自己在做什么。可以这样说foo/0.0:由于您使用0.0而不是明确地告诉编译器“这是浮点除法” 0,因此编译器信任您,并且不会发出警告。


1
两者都必须进行常规算术转换才能将它们转换为通用类型,这将使​​两种情况都浮点除法。
Shafik Yaghmour '18

@ShafikYaghmour您错过了答案的重点。请注意,我从未提及过的类型foo。那是故意的。仅在foo浮点类型的情况下,您的断言才是正确的。
卡西奥雷南

我没有,编译器具有类型信息并且可以理解转换,也许基于文本的静态分析器可能会被这类东西所吸引,但是编译器则不会。
Shafik Yaghmour '18

我的意思是,是的,编译器知道通常的算术转换,但是当程序员是显式的时,它选择不发出警告。整个问题是,这可能不是错误,而是故意的行为。
卡西奥·雷南

那么我指向的文档是不正确的,因为这两种情况都是浮点除法。因此,要么文档错误,要么诊断程序有错误。
Shafik Yaghmour

4

这看起来像gcc错误,的文档-Wno-div-by-zero 明确指出

不要警告编译时整数除以零。没有警告将浮点除以零,因为这可能是获得无穷大和NaN的合法方法。

[expr.arith.conv]中涉及的常规算术转换之后,两个操作数都将是double

许多期望算术或枚举类型的操作数的二进制运算符都以类似的方式引起转换并产生结果类型。目的是产生一个通用类型,它也是结果的类型。这种模式称为通常的算术转换,其定义如下:

...

否则,如果其中一个操作数为double,则另一个应转换为double。

[expr.mul]

*和/的操作数应为算术或非范围枚举类型;%的操作数应具有整数或无作用域的枚举类型。 通常对操作数进行算术转换,并确定结果的类型。

关于浮点除零是否为未定义行为以及不同的实现方式如何处理似乎是我的答案TL; DR; 看起来gcc符合附件F的浮点数除以零,所以undefined在这里不起作用。对于c来说答案是不同的。


2

浮点除以零的行为与整数除以零的行为不同。

IEEE浮点 + INF和-INF之间的标准分化带来,而整数不能存储无穷大。整数除以零结果是不确定的行为。浮点除以零是由浮点标准定义的,结果为+ inf或-inf。


2
的确如此,但是由于这两种情况都执行浮点除法,因此尚不清楚这与问题有何关系。OP的代码中没有整数除法。
康拉德·鲁道夫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.