Python 中复数指数运算在什么情况下会抛出 OverflowError?

4

我正在尝试弄清楚这里的模式:

>>> 1e300 ** 2
OverflowError: (34, 'Result too large')
>>> 1e300j ** 2
OverflowError: complex exponentiation
>>> (1e300 + 1j) ** 2
OverflowError: complex exponentiation
>>> (1e300 + 1e300j) ** 2
(nan+nanj)

这种行为在理论上似乎没有具体说明,而且在实践中也很奇怪! 这是什么原因?

此外,(1e300+1e300j) ** 100 ==> (nan+nanj); (1e300+1e300j) ** 101 ==> Traceback (most recent call last): File "<stdin>", line 1, in <module> OverflowError: complex exponentiation - vroomfondel
1e300是一个浮点数,因此它不会自动提升。但这个可以:(1*10**300)**2 - dawg
Jason:如果你有时间在http://bugs.python.org上开一个问题,我们将不胜感激。 - Mark Dickinson
1
@MarkDickinson:正确的结果是什么?哪里出了问题? - dawg
2
@drewk:不确定是否称其为错误(例如,可能没有意义为Python 2.7修复此问题),但有时引发异常,有时返回NaN和无穷大的不一致性令人不安。理想情况下,在所有情况下它应该只做其中一种(我个人倾向于使用异常)。对于Python 3.4修复这个问题是很好的。 - Mark Dickinson
3个回答

7

查看复杂指数的源代码,可以发现Python只在计算结束时检查溢出。此外,还有一个特殊情况,针对小整数指数使用平方幂运算,其中涉及复数乘法。

r.real = a.real*b.real - a.imag*b.imag;
r.imag = a.real*b.imag + a.imag*b.real;

这是复数乘法的公式。请注意以下几点:
a.real*b.real - a.imag*b.imag

ab非常大时,这将变成浮点无穷大减去浮点无穷大,结果为nannan的结果会传播,在几个操作后,结果是(nan+nanj)。如果只看到无穷大,Py_ADJUST_ERANGE2只会设置errno,因此它会忽略溢出并继续执行。

总之,Python仅检查最终结果是否溢出,而不检查中间值,这导致它错过了中间的溢出,因为最终都是nan。引发OverflowError的表达式从未尝试减去无穷大,因此错误在最后被发现。这似乎不是一个故意的设计决策;您可以通过更改溢出检查的方式来修复它。


2
-1:这是一个相当不错的答案,但是语句“引发OverflowError的表达式之所以会这样做,是因为它们从未尝试减去无穷大,因此错误被最终发现。”是完全错误的。 - the wolf
@thewolf:真的吗?有什么问题吗? - user2357112
1
我怀疑@thewolf将“The expressions that do raise OverflowError”解释为一般情况或有关Python操作的一般陈述,而不是这个问题中的具体示例。 - Eric Postpischil
啊,不,我只是在谈论问题中提到的那些。 - user2357112

3

好的,我很确定我已经解决了这个问题。

首先,我注意到了一个看起来有些荒谬的事情:

>>> (1e309j)**2
(nan+nanj)
>>> (1e308j)**2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: complex exponentiation

有意思。也许这与复数幂运算没有关系。
>>> (1e309)**2
inf
>>> (1e308)**2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: (34, 'Result too large')

更有趣的是,让我们来看看Python对1e3091e308的看法:

>>> (1e308)
1e+308
>>> (1e309)
inf

最后,
>>> (1e309)**2
inf
>>> (1e309j)**2
(nan+nanj)

并且
>>> (float('inf') + 1j) ** 2
(nan+nanj)

>>> (1e309j)
infj
>>> (1e309j)**2
(nan+nanj)

任何对inf的操作都会得到inf。看起来复数(a+bi)的实现不太倾向于给出inf,而更倾向于给出(nan+nanj)。因此,通常返回inf的东西会返回(nan+nanj)。我不确定为什么会这样,也许有更好地理解Python中inf和nan的人可以解释一下。
简而言之,数字最终会停止溢出并开始返回inf。使用inf进行计算很容易,但与其接近的数字的计算却不是!这就是为什么1e309**2有效而1e308 ** 2无效的原因。当与复数配对时,这种情况(出于某种原因)会导致(nan+nanj)。我只是通过在控制台上玩耍才知道了这一点——我很想看到更详细的解释!
注:@user2357112给出了一个更好的原因。计算复指数的方式可能包括计算inf-inf,结果为nan。我将保留这个模式以显示原因,但他的答案给出了解释。
顺便说一句,我发现这很有趣:
>>> (float('inf') + 1j) ** 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: complex exponentiation
>>> (float('inf') + 1j) 
(inf+1j)

这不是完整的分析。我已经在答案中发布了完整的分析。 - user2357112
1
@user2357112,实际上,您的解释与我的答案几乎一致--您只是更具体地解释了为什么我们得到了(nan - nanj) - vroomfondel

0

Python整数值可以自动提升为长整型以实现任意精度:

>>> (10**300)**2
1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

浮点数值溢出是由于IEEE浮点数的限制造成的:

>>> float(10**300)**2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: (34, 'Result too large')

Python 复数的组成部分是 float 类型,受相同的溢出限制:
>>> isinstance(complex(1).real,float)
True
>>> isinstance(complex(1).imag,float)
True

取通常的最大双精度值:

>>> max_double=1.7976931348623157e+308

在浮点范围内执行大多数增加该值的步骤,您将获得inf
>>> max_double*10
inf
>>> max_double*max_double
inf
>>> max_double*max_double*max_double*max_double
inf
>>> max_double++10**(308-15)
inf

如果在 FP 窗口之外,尾数和指数不会改变:

>>> md+10**(308-17)
1.7976931348623157e+308
>>> max_double**1.0000000000000001
1.7976931348623157e+308

溢出可以看到:

>>> max_double**1.000000000000001
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: (34, 'Result too large')

但正如文档中所述的,它并没有一致地应用:

由于C语言中浮点数异常处理缺乏标准化,大多数浮点运算也不会被检查。

这可以在此处看到:

>>> (1e300+1e300j)*(1e300+1e300j)
(nan+infj)
>>> (1e300+1e300j)**2
(nan+nanj)

这并没有回答问题。为什么(1e300+1e300j)**2不会引发OverflowError - user2357112
由于中间值溢出,请尝试 1.7976931348623157e+308*1.0000000000001 - user688635
@drewk 我之前给你的回答点了个踩是因为它似乎与问题完全无关。但现在我看到其相关性,我想改变我的投票,但除非你编辑答案,否则我不能这样做!由你决定。 - vroomfondel
这与什么有关呢?我们知道浮点数会溢出。我们知道复数用两个浮点数表示。当浮点数指数运算发生溢出时,Python应该引发OverflowError。问题是为什么(1e300+1e300j)**2 没有引发预期的OverflowError - user2357112

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