>>> (float('inf')+0j)*1
(inf+nanj)
为什么?这在我的代码中造成了一个讨厌的错误。
为什么1
乘法身份不给(inf + 0j)
?
>>> (float('inf')+0j)*1
(inf+nanj)
为什么?这在我的代码中造成了一个讨厌的错误。
为什么1
乘法身份不给(inf + 0j)
?
Answers:
首先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
其强制转换为1 + 0j
。
array([inf+0j])*1
也计算为array([inf+nanj])
。假设实际的乘法发生在C / C ++代码中的某个地方,这是否意味着他们编写了自定义代码来模拟CPython行为,而不是使用_Complex或std :: complex?
numpy
有一个中心类ufunc
,几乎每个运算符和函数都从该中心派生。ufunc
负责广播管理,使所有棘手的管理工作大步向前,使管理数组变得如此便捷。更确切地说,特定操作员和通用机器之间的工作分配是特定操作员为其要处理的输入和输出元素类型的每种组合实现了一组“最内层循环”。通用机械负责所有外部回路,并选择最匹配的内部回路...
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"
。
从机械上讲,公认的答案当然是正确的,但我认为可以给出更深层次的答案。
首先,像@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
。
现在,为了简单起见,复杂的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
所有无穷大集合在一起的函数时有相同的感觉。
这是一个相关问题,比我本人更有能力回答。
nan != nan
。我知道这个答案是在开玩笑,但我看不出为什么它应该对OP的编写方式有所帮助。
==
(并且他们接受了另一个答案),看来这只是OP如何表达标题的问题。我改写了标题以解决这种不一致的情况。(有意使该答案的前半部分无效,因为我同意@cmaster:这不是这个问题要问的问题)。
这是在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是相等的(见本),甚至有的还具有符号位组(因此打印为“ - ”,看到这),有些则没有。这至少是违反直觉的。
我的主要收获是:“简单”的复数乘法(或除法)并不简单,当在语言或什至是编译器之间切换时,人们必须为微妙的错误/差异做好准备。
printf
double的工作方式及类似用法的实现细节:他们查看符号位,以决定是否应打印“-”(无论是否为nan)。因此,您是对的,“ nan”和“ -nan”之间没有有意义的区别,很快就可以解决答案的这一部分。
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)
我知道的不是很直观...但是有时编码语言的定义方式与我们日常使用的方式不同。