为什么(inf + 0j)* 1计算为inf + nanj?


97
>>> (float('inf')+0j)*1
(inf+nanj)

为什么?这在我的代码中造成了一个讨厌的错误。

为什么1乘法身份不给(inf + 0j)


1
我认为您要查找的关键字是“ 字段 ”。默认情况下,加法和乘法是在单个字段中定义的,在这种情况下,唯一可以容纳您的代码的标准字段是复数字段,因此默认情况下,在操作良好之前,这两个数字都需要被视为复数-定义。这并不是说他们不能扩展这些定义,但是显然他们只是遵循标准的东西,并不希望自己竭尽全力来扩展这些定义。
user541686

1
哦,如果您发现这些特质令人沮丧并想要打孔您的计算机,则表示我同情
user541686

2
@Mehrdad一旦添加了这些非有限元素,它将不再是一个字段。确实,由于不再存在乘法中性,因此从定义上讲它也不能成为一个字段。
Paul Panzer

@PaulPanzer:是的,我想他们后来才提到这些内容。
user541686 '19

1
浮点数(即使不包括无穷大和NaN)也不是字段。多数适用于字段的身份不适用于浮点数。
–plugwash

Answers:


95

首先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

8
要回答“为什么……?”这个问题,最重要的步骤可能是第一步,将1其强制转换为1 + 0j
沃伦·韦克瑟

5
请注意,C99指定在乘以复杂类型时(标准草案的6.3.1.8节),真正的浮点类型不会提升为复杂类型,据我所知,C ++的std :: complex也是如此。这可能部分是出于性能原因,但也避免了不必要的NaN。
benrg

@benrg在NumPy中,array([inf+0j])*1也计算为array([inf+nanj])。假设实际的乘法发生在C / C ++代码中的某个地方,这是否意味着他们编写了自定义代码来模拟CPython行为,而不是使用_Complex或std :: complex?
marnix

1
@marnix比它更参与。numpy有一个中心类ufunc,几乎每个运算符和函数都从该中心派生。ufunc负责广播管理,使所有棘手的管理工作大步向前,使管理数组变得如此便捷。更确切地说,特定操作员和通用机器之间的工作分配是特定操作员为其要处理的输入和输出元素类型的每种组合实现了一组“最内层循环”。通用机械负责所有外部回路,并选择最匹配的内部回路...
Paul Panzer

1
...根据需要推广任何不完全匹配的类型。我们可以通过types属性访问提供的内部循环的列表,以np.multiply产生此收益,['??->?', 'bb->b', 'BB->B', 'hh->h', 'HH->H', 'ii->i', 'II->I', 'll->l', 'LL->L', 'qq->q', 'QQ->Q', 'ee->e', 'ff->f', 'dd->d', 'gg->g', 'FF->F', 'DD->D', 'GG->G', 'mq->m', 'qm->m', 'md->m', 'dm->m', 'OO->O']我们可以看到几乎没有混合类型,特别是没有将float "efdg"与complex 混合的类型"FDG"
Paul Panzer

32

从机械上讲,公认的答案当然是正确的,但我认为可以给出更深层次的答案。

首先,像@PeterCordes在注释中一样澄清问题是很有用的:“复数是否存在可用于inf + 0j的复数形式?” 或者换句话说就是OP认为在计算机实现复杂乘法方面存在弱点,或者在概念上有什么不完善的地方inf+0j

简短答案:

使用极坐标,我们可以将复数乘法视为缩放和旋转。即使将无限个“手臂”旋转0度(如乘以1的情况),我们也无法期望将其尖端以有限的精度放置。因此,确实存在一些根本不正确的东西inf+0j,即,一旦我们达到无穷大,有限的偏移就变得毫无意义。

长答案:

背景:这个问题所围绕的“大事”是扩展数字系统(考虑实数或复数)的问题。可能要这样做的原因之一是添加了无穷大的概念,或者如果恰好是数学家,则使“紧凑”。还有其他原因,太(https://en.wikipedia.org/wiki/Galois_theoryhttps://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会尽可能使其工作,包括弯曲如何在规则infnan相互作用(主要是inf胜过nan)。OP的问题是通过不将实数和提议的纯虚数类型提升为复数来避免的,但是让实数1与复数1的行为不同并不能解决我的问题。可以说,附件G没有充分说明两个无限性的乘积应该是什么。

我们可以做得更好吗?

试图通过选择更好的无限性几何来尝试解决这些问题。类似于扩展的实线,我们可以为每个方向添加一个无穷大。此构造类似于投影平面,但不会将相反的方向聚集在一起。无限性将以极坐标inf xe ^ {2 omega pi i}表示,定义乘积将很简单。特别是,OP的问题将很自然地解决。

但这就是好消息结束的地方。从某种意义上说,我们可以不拘一格地(而不是不合理地)要求我们的新式无限性支持提取其实部或虚部的函数。加法是另一个问题。添加两个非对映的无穷大,我们必须将角度设置为不确定nan(即(可以说该角度必须位于两个输入角度之间,但是没有简单的方式来表示“部分南度”))

黎曼来营救

鉴于所有这些,也许最好的做法是进行旧的一点压实。也许附件G的作者在强制要求将cproj所有无穷大集合在一起的函数时有相同的感觉。


这是一个相关问题,比我本人更有能力回答。


5
是的,因为nan != nan。我知道这个答案是在开玩笑,但我看不出为什么它应该对OP的编写方式有所帮助。
cmaster-恢复莫妮卡

考虑到问题正文中的代码实际上并未使用==(并且他们接受了另一个答案),看来这只是OP如何表达标题的问题。我改写了标题以解决这种不一致的情况。(有意使该答案的前半部分无效,因为我同意@cmaster:这不是这个问题要问的问题)。
彼得·科德斯

3
@PeterCordes会很麻烦,因为使用极坐标,我们可以将复数乘法视为缩放和旋转。即使将无限个“手臂”旋转0度(如乘以1的情况),我们也无法期望将其尖端以有限的精度放置。在我看来,这是比被接受的解释更深刻的解释,也是与nan!= nan规则相呼应的解释。
保罗·潘泽

3
C99指定真正的浮点类型在乘以复杂类型时不提升为复杂类型(标准草案的6.3.1.8节),据我所知,C ++的std :: complex也是如此。这意味着1是这些语言中这些类型的乘法身份。Python应该做同样的事情。我称其当前行为只是一个错误。
benrg

2
@PaulPanzer:我没有,但是基本概念是一个零(我称Z为零)将始终支持x + Z = x和x * Z = Z以及1 / Z = NaN,一个(正) (无穷小)将支持1 / P = + INF,一个(负无穷小)将支持1 / N = -INF,(无符号无穷小)将产生1 / U = NaN。通常,除非x是一个真整数,否则xx将为U,在这种情况下它将产生
Z。– supercat

6

这是在CPython中如何实现复杂乘法的实现细节。与其他语言(例如C或C ++)不同,CPython采用了一种较为简单的方法:

  1. 整数/浮点数被乘以复数
  2. 使用简单的学校公式,一旦涉及到无限数,它就不会提供预期的/预期的结果:
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未被提升为另一个副作用complexinf+0.0j1.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是相等的(见),甚至有的还具有符号位组(因此打印为“ - ”,看到),有些则没有。

这至少是违反直觉的。


我的主要收获是:“简单”的复数乘法(或除法)并不简单,当在语言或什至是编译器之间切换时,人们必须为微妙的错误/差异做好准备。


我知道有很多nan位模式。但是,不知道标志位。但是我的意思是-nan与nan有何不同?还是我应该说比nan与nan有更多不同?
Paul Panzer

@PaulPanzer这只是printfdouble的工作方式及类似用法的实现细节:他们查看符号位,以决定是否应打印“-”(无论是否为nan)。因此,您是对的,“ nan”和“ -nan”之间没有有意义的区别,很快就可以解决答案的这一部分。
ead

啊好。担心莫,我以为我知道的关于fp的一切实际上都不正确...
Paul Panzer

抱歉,很烦人,但您确定这是“没有虚数的1.0,即1.0j与乘法不等于0.0 + 1.0j”。是正确的?该附件G似乎指定了纯粹的虚构类型(G.2),并规定了应如何相乘等(G.5.1)
Paul Panzer

@PaulPanzer不,谢谢您指出问题!作为C ++编码器,我大多数时候都通过C ++玻璃看到了C99标准-这让我无所适从,因为C在这里领先一步-您显然是对的,再次。
ead

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 = nanjinf*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)

我知道的不是很直观...但是有时编码语言的定义方式与我们日常使用的方式不同。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.