被零除:在C和/或C++中是未定义行为还是实现定义?

32
关于除以零的问题,标准规定如下:

C99 6.5.5p5 - / 运算符的结果是第一个操作数除以第二个操作数的商; % 运算符的结果是余数。在这两种操作中,如果第二个操作数的值为零,则行为未定义。

C++03 5.6.4 - 二元 / 运算符产生商,二元 % 运算符产生第一个表达式除以第二个表达式的余数。如果 / 或 % 的第二个操作数为零,则行为未定义。

如果我们只看上面的段落,答案对于这两种语言显然都是“未定义行为”。然而,如果我们在 C99 标准的后面继续阅读,就会看到以下看似自相矛盾的段落(1):

C99 7.12p4 - 如果可用,宏 INFINITY 扩展为 float 类型的常量表达式,表示正无穷大或无符号无限大;

那么,标准是否有某种“黄金法则”,即未定义行为不能被(潜在地)自相矛盾的陈述所取代呢?如果没有的话,那么我认为,如果你的实现定义了 INFINITY 宏,那么除以零就被定义为这样。然而,如果你的实现没有定义这样的宏,那么行为是未定义的。
我很好奇对于这个问题,这两种语言是否有共识(如果有的话)。如果我们讨论整数除法 `int i = 1 / 0` 和浮点数除法 `float i = 1.0 / 0.0`,答案会改变吗?
注意(1):C++03 标准讨论了 `` 库,其中包括 INFINITY 宏。

1
这是另一个众所周知的笑话:如果 x * 0 = y,那么如何找到 x? - psihodelia
我认为得出这样的结论并不是不合理的:如果你的实现定义了INFINITY宏,那么零除以零就被定义为这样。但事实上,这是不合理的,因为C99 7.12p4没有提到除法。无论如何,分配INFINITY与UB并不矛盾。 - user1196549
2
我投了反对票,因为我完全看不出第二个引用暗示无限大是除以零的结果,所以我认为这个问题是不恰当的。 - Antonio
2
有关浮点数除以零的行为 - Antonio
我在@Antonio提到的问题中添加了一个新答案,其中包含更多细节。 - Shafik Yaghmour
显示剩余4条评论
8个回答

33

我没有看到任何矛盾之处。除零是未定义的,没有任何引用文本中提到“...除非定义了无穷大”。

请注意,在数学中从未定义1/0 = 无穷大。一个人可能会这样解释,但这是一种个人“捷径”风格的解释,而不是一个可靠的事实。


1
同意。1 / 0;和类似的表达式绝不涉及宏INFINITY(除非实现自愿选择使用它来解决未定义的行为)。 - Matthew Flaschen
@PéterTörök,在数学中,1/0的答案是什么? - Pacerier
哇...数学与语言定义有什么关系?当程序说int x = 1 / 0;时,计算机会发生什么?计算机会采摘雏菊吗?还是(几乎所有我听说过的平台都是这样)我们会崩溃并且失败得很惨?“1/0是什么”这个问题的答案可能是未定义的,但是异常程序终止异常不是未定义的。标准真的还这么松散吗? - user645280
不仅标准非常宽松,而且在过去几年中,解释力度变得更加松散。过去,当一个平台在不受标准规定情况下自然表现出某种行为时,编译器会允许程序员利用它。然而,超现代的编译器理念表明,在给定需要这些特性满足规定输入的程序时,更好的做法是生成一个更小的可执行文件,它将在所有指定情况下遵守C标准... - supercat
比起一个更大的可执行文件,该文件可以满足甚至在C标准未规定的情况下的需求。 - supercat
显示剩余6条评论

16

1 / 0不是无穷大,只有

x -> +0时 lim 1/x = ∞


5
这个答案与编程无关。 - Pete Kirkham
24
这是一个在问题范围内完全有效的答案。 - Peter Alexander
10
这是不正确的,只有当x被限制为正值时才成立。 - interjay
5
@Pete Alexander 这个问题是关于在C和/或C++中,除以零的行为是“未定义行为”还是“实现定义”的,而不是标准中的行为是否符合数学概念。该标准对于这种极限情况使用HUGE_VAL,它与IEEE745实现中的无穷大具有相同的位模式。 - Pete Kirkham
2
@Pete Kirkham:嗯,有一个标准回答了这个问题,还有另一个标准,它并不与第一个标准相矛盾,只是因为错误的数学假设而被认为是矛盾的,所以我认为拒绝这种假设可以使答案清晰明了。 - František Žiačik
显示剩余7条评论

11

这不是一个数学纯粹主义的问题,而是一个 C/C++ 的问题。

  • 根据 IEEE 754 标准,所有现代 C 编译器/浮点运算单元使用的标准,我们有下面的计算结果:
    • 3.0 / 0.0 = INF
    • 0.0 / 0.0 = NaN
    • -3.0 / 0.0 = -INF

如果需要的话,FPU 可以有一个状态标志来生成异常,但这并不是常见做法。

当 INF 是一个有用的结果时,INF 可以非常有用以避免分支。详见这里的讨论。


4

为什么会这样呢?

从数学上来说,这并不合理,1/x在数学中并没有被定义为∞。此外,你至少需要增加两种情况:-1/x和0/x也不能等于∞。

请参阅一般的除以零,特别是计算机算术部分。


我认为当x趋近于0时,y = 1/x 等于 ∞。 - SiegeX
3
这是一个非常常见的错误(我猜那些从未接受过正式微积分课程的人更有可能犯这种错误)。SiegeX,C/C++没有“ --> ”的概念。 - shoosh
@shoosh:你是说 lim [x --> +0] f(y) != ∞,其中 f(y) = 1/x 吗? - SiegeX
1
@shoosh:当然有,你没看到这个 Stack Overflow 的问题吗?https://dev59.com/RXI-5IYBdhLWcg3w18d3? =P - SiegeX
1
@SiegeX:我认为在数学中,0不能作为除数。当x为正数时,y=1/x趋近于无穷大;当x为负数时,y=1/x趋近于负无穷大。但是,我们不能选择其中任何一个答案,因为在数学上,除法是通过乘法来定义的,即d=p/q被定义为乘以q得到p的数字,而任何数乘以0都只能得到0。 - JeremyP

3
实现定义了__STDC_IEC_559__的要遵守Annex F中给出的要求,这要求浮点语义与IEC 60559一致。标准对于未定义__STDC_IEC_559__的实现在浮点除以零的行为上没有任何要求,但对于定义了它的实现则有要求。在IEC 60559指定了行为而C标准没有指定的情况下,定义了__STDC_IEC_559__的编译器必须按照IEC标准描述的行为执行,这是C标准的要求。
根据IEC 60559(或美国标准IEEE-754)的定义,零除以零的结果为NaN,一个浮点数除以正零或字面常量零的结果为一个具有与被除数相同符号的INF值,而一个浮点数除以负零的结果则为一个带有相反符号的INF。

1

我只有C99草案。在§7.12/4中,它说:

The macro

    INFINITY

expands to a constant expression of type float representing positive or unsigned infinity, if available; else to a positive constant of type float that overflows at translation time.

请注意,INFINITY 可以根据浮点溢出而定义,而不一定是除以零。

1
关于INFINITY宏:在IEEE754标准中,有一个明确的编码表示+/-无穷大,即如果所有的指数位都设置,所有的分数位都被清除(如果分数位被设置,则表示NaN)。
使用我的编译器,(int) INFINITY == -2147483648,因此如果返回INFINITY,表达式int i = 1/0将肯定产生错误的结果。

0
底线是,根据你的引用,C99在“实现定义”上并没有提到INFINITY。其次,你引用的内容并没有显示“未定义行为”的不一致含义。

[引用维基百科的未定义行为页面]“在C和C ++中,还使用实现定义的行为,其中语言标准没有指定行为,但实现必须选择一种行为并需要记录并遵守所选择的规则。”

更确切地说,标准在使用“实现定义”这些词时是指“实现定义”(我认为只有这样),因为“实现定义”是标准的一个特定属性。 C99 7.12p4的引用没有提到“实现定义”。

[来自C99 std(晚期草案)]“未定义行为:在使用不可移植或错误的程序结构或错误数据时的行为,对于此国际标准不强制执行任何要求”

请注意,“未定义行为”没有强制执行任何要求!

[C99 ..]“实现定义的行为:未指定的行为,其中每个实现都记录了如何进行选择”

[C99 ..]“未指定的行为:使用未指定的值或其他行为,在这种情况下,国际标准提供两个或多个可能性,并且对于选择哪个实例不再有进一步的要求”

文档是实现定义行为的要求。


如果一个实现定义了__STDC_IEC_559__,即使C标准不会否则强制执行任何行为要求,它也必须实现与该标准一致的除零语义。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接