问题:为什么(inf + 0j)* 1计算为inf + nanj?
>>> (float('inf')+0j)*1
(inf+nanj)
为什么?这在我的代码中造成了一个讨厌的错误。
为什么1
乘法身份不给(inf + 0j)
?
回答 0
首先1
将转换为复数1 + 0j
,然后再进行inf * 0
乘法运算,结果为nan
。
(inf + 0j) * 1
(inf + 0j) * (1 + 0j)
inf * 1 + inf * 0j + 0j * 1 + 0j * 0j
# ^ this is where it comes from
inf + nan j + 0j - 0
inf + nan j
回答 1
从机械上讲,公认的答案当然是正确的,但我认为可以给出更深层次的答案。
首先,像@PeterCordes在注释中一样澄清问题是很有用的:“复数是否存在可用于inf + 0j的复数形式?” 或者换句话说就是OP认为在计算机实现复杂乘法方面存在弱点,或者在概念上有什么不完善的地方inf+0j
简短答案:
使用极坐标,我们可以将复数乘法视为缩放和旋转。即使将无限个“手臂”旋转0度(如乘以1的情况),我们也无法期望将其尖端以有限的精度放置。因此,确实存在一些根本不正确的东西inf+0j
,即,一旦我们达到无穷大,有限的偏移就变得毫无意义。
长答案:
背景:这个问题所围绕的“大事”是扩展数字系统(考虑实数或复数)的问题。可能要这样做的原因之一是添加了无穷大的概念,或者如果恰好是数学家,则使“紧凑”。还有其他原因,太(https://en.wikipedia.org/wiki/Galois_theory,https://en.wikipedia.org/wiki/Non-standard_analysis),但我们不会在这里的那些兴趣。
一点压实
当然,关于这种扩展的棘手的一点是,我们希望这些新数字适合现有的算法。最简单的方法是在无穷大处添加一个元素(https://en.wikipedia.org/wiki/Alexandroff_extension),并使它等于零除以零。这适用于实数(https://en.wikipedia.org/wiki/Projectively_extended_real_line)和复数(https://en.wikipedia.org/wiki/Riemann_sphere)。
其他扩展…
尽管单点压缩是简单的并且在数学上是合理的,但是已经寻求了包括多个限定的“更丰富”的扩展。实际浮点数的IEEE 754标准具有+ inf和-inf(https://en.wikipedia.org/wiki/Extended_real_number_line)。看起来自然而直接,但是已经迫使我们跳过了圈并发明了-0
https://en.wikipedia.org/wiki/Signed_zero
…复杂平面的
复杂平面的扩展超过一英寸呢?
在计算机中,复数通常是通过将两个fp实数粘贴在一起来实现的,一个实数粘贴一个虚数部分。只要一切都是有限的,那是完全可以的。但是,一旦考虑到无限性,事情就会变得棘手。
复平面具有自然的旋转对称性,这与复数算法很好地联系在一起,因为将整个平面乘以e ^ phij与绕φ的旋转相同0
。
那附件G的东西
现在,为了简单起见,复杂的fp仅使用基础实数实现的扩展名(+/- inf,nan等)。这种选择似乎很自然,甚至没有被视为一种选择,但让我们仔细研究一下它的含义。复杂平面的此扩展的简单可视化效果类似于(I =无限,f =有限,0 = 0)
I IIIIIIIII I
I fffffffff I
I fffffffff I
I fffffffff I
I fffffffff I
I ffff0ffff I
I fffffffff I
I fffffffff I
I fffffffff I
I fffffffff I
I IIIIIIIII I
但是,由于真正的复数平面是尊重复数乘法的平面,因此可以提供更多信息
III
I I
fffff
fffffff
fffffffff
I fffffffff I
I ffff0ffff I
I fffffffff I
fffffffff
fffffff
fffff
I I
III
在此投影中,我们看到无限大的“不均匀分布”不仅丑陋,而且还遭受了OP类型问题的根源:大多数无限大(((+/- inf,有限)形式和(有限,+ / -inf)集中在四个主要方向上,所有其他方向仅由四个无穷大(+/- inf,+ -inf)表示。将复数乘法扩展到此几何体是一场噩梦,这不足为奇。
在C99规范的附录G会尽可能使其工作,包括弯曲如何在规则inf
和nan
相互作用(主要是inf
胜过nan
)。OP的问题是通过不将实数和提议的纯虚数类型提升为复数来避免的,但是让实数1与复数1的行为不同并不能解决我的问题。可以说,附件G没有充分说明两个无限性的乘积应该是什么。
我们可以做得更好吗?
试图通过选择更好的无限性几何来尝试解决这些问题。类似于扩展的实线,我们可以为每个方向添加一个无穷大。此构造类似于投影平面,但不会将相反的方向聚集在一起。无限性将以极坐标inf xe ^ {2 omega pi i}表示,定义乘积将很简单。特别是,OP的问题将很自然地解决。
但这就是好消息结束的地方。从某种意义上说,我们可以不拘一格地(而不是不合理地)要求我们的新式无限性支持提取其实部或虚部的函数。加法是另一个问题。添加两个非对映的无穷大,我们必须将角度设置为不确定nan
(即(可以说该角度必须位于两个输入角度之间,但是没有简单的方式来表示“部分南度”))
黎曼来营救
鉴于所有这些,也许最好的做法是进行旧的一点压实。也许附件G的作者在强制要求将cproj
所有无穷大集合在一起的函数时有相同的感觉。
这是一个相关问题,比我本人更有能力回答。
回答 2
这是在CPython中如何实现复杂乘法的实现细节。与其他语言(例如C或C ++)不同,CPython采用了一种较为简单的方法:
- 整数/浮点数被乘以复数
- 使用简单的学校公式,一旦涉及到无限数,它就不会提供预期的/预期的结果:
Py_complex
_Py_c_prod(Py_complex a, Py_complex b)
{
Py_complex r;
r.real = a.real*b.real - a.imag*b.imag;
r.imag = a.real*b.imag + a.imag*b.real;
return r;
}
上述代码的一种有问题的情况是:
(0.0+1.0*j)*(inf+inf*j) = (0.0*inf-1*inf)+(0.0*inf+1.0*inf)j
= nan + nan*j
但是,人们希望得到这样的-inf + inf*j
结果。
在这方面,其他语言不是遥不可及:复数乘法很长一段时间以来都不是C标准的一部分,仅作为附录G包含在C99中,该附录G描述了应如何执行复数乘法-而且它不像上面的学校公式!C ++标准没有指定复杂乘法的工作方式,因此大多数编译器实现都回落到C实现上,这可能符合C99(gcc,clang)或不符合(MSVC)。
对于上述“问题”示例,符合C99的实现(比学校公式更复杂)将提供(请参见live)预期结果:
(0.0+1.0*j)*(inf+inf*j) = -inf + inf*j
即使使用C99标准,也没有为所有输入定义明确的结果,即使对于符合C99的版本也可能有所不同。
在C99 中float
未被提升为另一个副作用complex
是inf+0.0j
与1.0
或相乘1.0+0.0j
会导致不同的结果(请参见此处实时显示):
(inf+0.0j)*1.0 = inf+0.0j
(inf+0.0j)*(1.0+0.0j) = inf-nanj
,虚部是-nan
和不是nan
(作为CPython的)不会在这里发挥作用,因为所有的安静NaN是相等的(见本),甚至有的还具有符号位组(因此打印为“ – ”,看到这),有些则没有。
这至少是违反直觉的。
我的主要收获是:“简单”的复数乘法(或除法)并不简单,当在语言或什至是编译器之间切换时,人们必须为微妙的错误/差异做好准备。
回答 3
Python的有趣定义。如果我们用笔和纸解决此问题,我会说预期的结果将expected: (inf + 0j)
如您所指出的那样,因为我们知道我们的意思是1
这样(float('inf')+0j)*1 =should= ('inf'+0j)
:
但是事实并非如此,当您运行它时,我们得到:
>>> Complex( float('inf') , 0j ) * 1
result: (inf + nanj)
Python的理解这*1
是一个复杂的数量和不规范的做法1
,因此解释为*(1+0j)
,当我们尝试做错误出现inf * 0j = nanj
如inf*0
不能得到解决。
您实际想要做什么(假设1是1的范数):
回想一下,如果z = x + iy
是具有实部x和虚部y的复数,则将的复共轭z
定义为z* = x − iy
,将绝对值(也称为norm of z
)定义为:
假设1
是正常的1
,我们应该做的是这样的:
>>> c_num = complex(float('inf'),0)
>>> value = 1
>>> realPart=(c_num.real)*value
>>> imagPart=(c_num.imag)*value
>>> complex(realPart,imagPart)
result: (inf+0j)
我知道的不是很直观…但是有时编码语言的定义方式与我们日常使用的方式不同。