Haskell 和 Python 中复数实现的区别

Differences between complex number implementations in Haskell and Python

我正在尝试将 Python 中的复数功能映射到 Haskell 中的 Data.Complex,但我已经达到了它们不同的程度,我不确定至于为什么。

在python中:

>>> x = 3j
3j
>>> x.real
0.0
>>> x.imag
3.0

在Haskell中:

> import Data.Complex
> let j n = 0 :+ n
> let x = j 3.0
> realPart x
0.0
> imagPart x
3.0

到目前为止,它们看起来是一样的。看起来对它们的操作也没有太大区别:

Python:

>>> y = 1 + x
(1+3j)
>>> y.real
1.0
>>> y.imag
3.0

Haskell:

> let y = 1 + x
> realPart y
1.0
> imagPart y
3.0

单独来看 + - * / ** 所有这些似乎都以相同的方式工作。但是,此操作会产生两个不同的结果:

>>> z = (y - 1) ** 2
(-9+0j)
>>> z.real
-9.0
>>> z.imag
0.0

但是在Haskell:

> let z = (y - 1) ** 2
> realPart z
-9.000000000000002
> imagPart z
1.1021821192326181e-15

这是为什么?

在Haskell中,(**) for Complex本质上是

a ** b = exp (b * log a)

有很多机会出现错误的舍入错误。(我不太了解 Python 来检查它会用类似的 log-then-exp 表达式做什么;我试过的东西抱怨它还没有准备好处理 log(3j)。)它有很多特殊情况来阻止舍入错误,但是 none 检查一个完全实数的整数指数。您可能认为这是一个错误或不当之处,并将其报告给负责 Complex 类型的人员,作为另一个值得添加到 (**).

实现中的特殊情况

同时,如果您知道指数是整数,则可以使用 (^)(仅适用于正数)或 (^^)

Data.Complex> (0 :+ 3) ^ 2
(-9.0) :+ 0.0

虽然两种语言给出的结果不同,但它们非常不同(正如其他人在评论中指出的那样)。所以您可能会猜想这只是实现方式略有不同的问题——您是对的。

表示在Haskell中,**运算符定义为

a ** b = exp (b * log a)

Haskell 做了一些特殊的大小写,但大多数时候,该操作依赖于 explog 对复数的通用定义。

在 Python 中有点不同:使用 polar 表示法计算幂。这种方法涉及使用一组不同的通用函数——其中大多数是普通浮点数上的基本三角函数——并且几乎不使用特殊外壳。我不清楚这种方法总体上是否更好,但它确实会在您选择的特定情况下给出更正确的答案。

这是core of the implementation

vabs = hypot(a.real,a.imag);
len = pow(vabs,b.real);
at = atan2(a.imag, a.real);
phase = at*b.real;
if (b.imag != 0.0) {
    len /= exp(at*b.imag);
    phase += b.imag*log(vabs);
}
r.real = len*cos(phase);
r.imag = len*sin(phase);

这里,a是底数,b是指数。 vabsat 给出了 a 的极坐标表示,这样

a.real = vabs * cos(at)
a.imag = vabs * sin(at)

正如您在最后两行代码中看到的,lenphase 给出了结果的相应极坐标表示,r

b为实数时,if块不执行,这简化为De Moivre's formula。我找不到涵盖复杂或虚构情况的规范公式,但它看起来很简单!