不同值类型之间的除零行为不一致

Posted

技术标签:

【中文标题】不同值类型之间的除零行为不一致【英文标题】:Inconsistency in divide-by-zero behavior between different value types 【发布时间】:2011-06-04 07:51:08 【问题描述】:

请考虑以下代码和 cmets:

Console.WriteLine(1 / 0); // will not compile, error: Division by constant zero

int i = 0;
Console.WriteLine(1 / i); // compiles, runs, throws: DivideByZeroException

double d = 0;
Console.WriteLine(1 / d); // compiles, runs, results in: Infinity   

我可以理解编译器会在运行时主动检查除以零常量和 DivideByZeroException,但是:

为什么在被零除中使用双精度数会返回 Infinity 而不是抛出异常?这是设计使然还是错误?

只是为了好玩,我在 VB.NET 中也这样做了,结果“更一致”:

dim d as double = 0.0
Console.WriteLine(1 / d) ' compiles, runs, results in: Infinity

dim i as Integer = 0
Console.WriteLine(1 / i) '  compiles, runs, results in: Infinity

Console.WriteLine(1 / 0) ' compiles, runs, results in: Infinity

编辑:

根据 kekekela 的反馈,我运行了以下结果:

Console.WriteLine(1 / .0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001);

这个测试似乎证实了这个想法,0.0 的字面双倍实际上是一个非常非常小的分数,它将导致无穷大......

【问题讨论】:

这是我关于这个主题的文章:blogs.msdn.com/b/ericlippert/archive/2009/10/15/… 【参考方案1】:

double 是一个浮点数,而不是一个精确的值,所以从编译器的角度来看,你真正除以的是接近零的东西,但不完全是零。

【讨论】:

实际上,双精度数确实有一个完全为零的值的表示。真正的原因是,根据定义,双精度数除以零会导致 Inf 的值——这是设计使然,而不是偶然。 @slebetman - 如果双打具有“一个完全为零的值的表示”,那么为什么双打区分“+0”和“-0”以及为什么 1/+0 和 1 /-0 给出不同的结果?只有当这些值被视为太小而无法正常表示的正值或负值时,“有符号零”的想法才有意义。请注意,IEEE 754 浮点类型中没有无符号零。 @Jeffrey:+0 和 -0 仍然是 0。即使您认为小于 epsilon 的数字用 0 表示,您仍然是在说太小的数字实际上是 0。是的,在数学上 -0 没有意义,但在设计 754 时,运行模拟的物理学家认为,如果计算的极限导致 0,他们想知道结果来自哪个方向。 @slebetman - 1/+0 = +INF 但另一方面,1/-0 = -INF。如果它们在计算中导致不同的结果,那么 +0 和 -0 怎么可能都为零?此外,+0 与 -0 一样没有意义。零只是零。它根本没有任何迹象。除非将这些值视为非常小的非零值,否则浮点数的行为毫无意义。但如果这样考虑,那就完全合情合理了。 计算机通常不会像在学校教的那样执行“正常”数学,因为这对计算机来说真的很难做到。相反,它们通常实现 IEEE 754 浮点标准,该标准相似但有一些细微差别。一个区别是有两个零,+0 和 -0,它们比较相等。他们都是零。有两个零表示显然会使一些计算机算法更快/更简单。【参考方案2】:

这可能与 IEEE 标准浮点数和双精度浮点数具有指定的“无穷大”值有关。 .NET 只是在硬件层面暴露了一些已经存在的东西。

请参阅 kekekela 的回答,了解为什么这是合乎逻辑的。

【讨论】:

【参考方案3】:

这是设计使然,因为 double 类型符合浮点运算标准 IEEE 754。查看Double.NegativeInfinity 和Double.PositiveInfinity 的文档。

这个常数的值是正或负数除以零的结果。

【讨论】:

【参考方案4】:

简而言之:double 类型定义了一个无穷大值,而 int 类型没有。所以在double 的情况下,计算的结果是一个值,你可以在给定类型中实际表达它,因为它已定义。在int 的情况下,无穷大没有价值,因此无法返回准确的结果。因此例外。

VB.NET 做的事情有点不同;整数除法使用/ 运算符自动生成浮点值。这是为了允许开发人员编写,例如,表达式1 / 2,并让它评估为0.5,有些人会认为这是直观的。如果您想查看与 C# 一致的行为,请尝试以下操作:

Console.WriteLine(1 \ 0)

注意上面使用整数除法 运算符(\,而不是/)。我相信你会得到一个异常(或编译错误——不确定是哪个)。

同样,试试这个:

Dim x As Object = 1 / 0
Console.WriteLine(x.GetType())

上面的代码会输出System.Double

至于关于不精确的观点,这是另一种看待它的方式。并不是double 类型没有完全为零的值(它确实如此);相反,double 类型并不意味着首先提供数学上精确的结果。 (某些值可以精确表示,是的。但是 计算 不能保证准确性。)毕竟,数学表达式 1 / 0 的值没有定义(最后我检查了)。但是1 / x 在 x 接近零时接近无穷大。因此,从这个角度来看,如果我们无论如何都不能表示大多数分数n / m准确,那么将x / 0 的情况视为近似并给出它的值是有意义的接近--同样,至少定义了无穷大。

【讨论】:

虽然很好用,但这个答案也有点错误。答案是错误的,因为除以零不是无穷大 - 它在数学上是未定义的。真正的答案是双打不是实数(R),正如本答案后半部分所指出的那样。它们是浮点数,OP 正试图对非实数应用实数推理。它们在很多时候看起来很相似,因为它们被设计为相似,但它们本质上是不同的。双打定义了一个叫做NaN的东西,“不是数字”,(继续...) (cont...) 如果这实际上是实数,这将是一个更数学正确的“结果”。然而它没有返回 NaN。原因大致如答案中所述:因为在浮点逻辑中,您主要假设“0.0”是一个非常小的数字。但是,您不能说它 is 是一个小数字,说明存在 is 并且零的精确表示具有误导性,因为没有从 float 进行的一对一映射到 R。相反,每个浮点值映射到一个实数值范围,并且浮点“0.0”包括 实际其他小值的范围。 另一个浮点数与实数根本不同的例子是浮点数定义“-0.0”(负零),并且将 OP 除以结果将是 @987654340 @。然而,您认为 0.0 == -0.0 的评估结果是什么?【参考方案5】:

因为“数字”浮点不是那种类型的。浮点运算:

不具有关联性 不是分布式的 可能没有乘法逆元

(请参阅http://www.cs.uiuc.edu/class/fa07/cs498mjg/notes/floating-point.pdf 了解一些示例)

浮点是一种用于解决特定问题的结构,并且在不应该使用的时候被全部使用。我认为它们非常糟糕,但这是主观的。

【讨论】:

以上是关于不同值类型之间的除零行为不一致的主要内容,如果未能解决你的问题,请参考以下文章

具有内部链接的变量的暂定定义具有不完整的非数组类型:一致的实现显示不同的行为

NSManagedObject 子类之间看似不一致的行为

哈希可枚举方法:仅传递一个参数时的行为不一致

iOS 14 中设备之间的小部件行为不一致

jenkins构建中的除零错误

重载和重写