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 做了一些特殊的大小写,但大多数时候,该操作依赖于 exp
和 log
对复数的通用定义。
在 Python 中有点不同:使用 polar 表示法计算幂。这种方法涉及使用一组不同的通用函数——其中大多数是普通浮点数上的基本三角函数——并且几乎不使用特殊外壳。我不清楚这种方法总体上是否更好,但它确实会在您选择的特定情况下给出更正确的答案。
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
是指数。 vabs
和 at
给出了 a
的极坐标表示,这样
a.real = vabs * cos(at)
a.imag = vabs * sin(at)
正如您在最后两行代码中看到的,len
和 phase
给出了结果的相应极坐标表示,r
。
当b
为实数时,if
块不执行,这简化为De Moivre's formula。我找不到涵盖复杂或虚构情况的规范公式,但它看起来很简单!
我正在尝试将 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
虽然两种语言给出的结果不同,但它们非常不同(正如其他人在评论中指出的那样)。所以您可能会猜想这只是实现方式略有不同的问题——您是对的。
**
运算符定义为
a ** b = exp (b * log a)
Haskell 做了一些特殊的大小写,但大多数时候,该操作依赖于 exp
和 log
对复数的通用定义。
在 Python 中有点不同:使用 polar 表示法计算幂。这种方法涉及使用一组不同的通用函数——其中大多数是普通浮点数上的基本三角函数——并且几乎不使用特殊外壳。我不清楚这种方法总体上是否更好,但它确实会在您选择的特定情况下给出更正确的答案。
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
是指数。 vabs
和 at
给出了 a
的极坐标表示,这样
a.real = vabs * cos(at)
a.imag = vabs * sin(at)
正如您在最后两行代码中看到的,len
和 phase
给出了结果的相应极坐标表示,r
。
当b
为实数时,if
块不执行,这简化为De Moivre's formula。我找不到涵盖复杂或虚构情况的规范公式,但它看起来很简单!