为什么在Python中,除以接近零的数会有不同的行为?

4

这其实并不是一个问题,而是Python实现的浮点运算中有趣的现象。

有人能解释一下以下行为吗?

>>> 1/1e-308
1e+308
>>> 1/1e-309
inf
>>> 1/1e-323
inf
>>> 1/1e-324
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: float division by zero

似乎将1除以接近于零的数会得到“inf”,如果更接近“ZeroDivisionError”就会抛出异常。这似乎是一种奇怪的行为。
在Python 2.x/3.x中输出相同结果。
编辑:我主要的问题是为什么在某些范围内我们会得到“inf”,而不是ZeroDivisionError,假设Python似乎将1e-309视为零。
3个回答

7

这与IEEE754浮点格式本身有关,而不是Python的实现。

一般来说,浮点数可以表示比较小的负指数,而不能表示太大的正指数,因为存在非规格化数。这种情况下,浮点数的尾数部分不再隐含地以1开头,而是描述整个尾数,并以零开头。如果你不知道这是什么,请阅读有关浮点数表示的文章,可以从这里开始。

因此,当你倒置一个非规格化数时,可能会得到一个过大而无法表示的正指数。计算机会用inf代替它。你例子中的1e-308也是非规格化的,但当倒置时仍然不会太小而溢出(因为在正常数中,标准实际上允许略大于负指数的正指数)。

对于1e-324,那个数字太小了,即使作为非规格化数也无法表示,所以该浮点数字面值实际上等于零。这就是为什么你会得到除以零的结果。可表示的最小64位浮点数是(略低于)5e-324


1
这完全与Python对IEEE 754的(非)实现有关。一个理智的编程语言应该允许除法发生(处理器正确实现了它,不像Python设计者),并产生适当的结果。相反,Python明确测试除数是否为零,并在这种情况下表现不同。这需要额外的周期,不能防止溢出发生,并且意味着Python不符合IEEE 754标准(因为在IEEE 754中,1.0 / 0.0会产生+inf)。 - Pascal Cuoq
1
@PascalCuoq:从技术上讲,这并不完全正确,因为如果启用了陷阱,使用IEEE754算术会导致异常。然而,由于这远非默认行为,因此您的观点是有效的、值得注意的。但是,两种行为之间的差异是由于反规格化数的倒置导致溢出被产生,因此我认为答案不需要重新表述。 - Dolda2000

3

Python中可以用作浮点数字的最小值为:

2.2250738585072014e-308

Python使用双精度浮点数,其可容纳从约10的-308次方到10的308次方的值。

维基百科 - 双精度浮点格式

实际上,您可能可以通过非规格化数获得比1e-308还要小的数字,但这会带来显著的性能损失。我发现Python可以处理1e-324,但在1e-325下溢并返回0.0作为值。


谢谢,我明白了。然而,我还不明白为什么我们在一个小范围内得到inf而不是像Python应该返回的ZeroDivisionError。这可能是语言设计中有意为之的吗? - emartinelli
在Python shell中,我测试了1e-323不等于零,但是1e-324等于零,因此会引发除以零的错误。正如我提到的,Python中浮点数的最小值为2.22e-308,比这个值更小的数值Python不会进行任何数学计算,只是使用try catch来避免引发divideByZero错误。 - ᴀʀᴍᴀɴ
1
“但是这会对性能产生显著影响”--仅供记录,这并不一定正确。我相信大多数来自英特尔和AMD的大型架构都可以处理denormals、infinities和NaNs而不会有额外的惩罚。此外,也应该指出,与运行解释的Python代码相比,惩罚将非常微小。;) - Dolda2000

2

大部分内容已经由Dolda2000在他的答案中解释了。然而,看一下可能会更有帮助。

>>> 1e-308
1e-308
>>> 1e-309
1e-309
>>> 1e-323
1e-323
>>> 1e-324
0.0

正如你所看到的,在Python实现中,1e-324等于0.0。正如Dolda2000所说的那样:这个数字太小了,即使作为非规格化数表示也不行,因此浮点字面值实际上等于零。

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