cmath 和 numpy 都给出了 asin(10) 的“不正确”值

Mik*_*r67 5 python math numpy

最近需要快速求10的反正弦,决定用python来帮我计算:

cmath.asin(10)
Run Code Online (Sandbox Code Playgroud)

根据经验,我预计结果会出现在第四象限(正实数(pi/2)和负虚数)。惊喜......它返回了第一象限结果。我也试过 numpy.arcsin ......同样的结果。虽然返回值的正弦值确实是 10,但我认为这不是反正弦函数的标准主值

>>> import math
>>> import numpy as np
>>> z=cmath.asin(10)
>>> z
(1.5707963267948966+2.993222846126381j)
>>> cmath.sin(z)
(9.999999999999998+6.092540900222251e-16j)
>>> z2=np.arcsin(10+0j)
>>> np.sin(z2)
(10+6.092540900222253e-16j)
Run Code Online (Sandbox Code Playgroud)

我使用 numpy 发现了同样的结果(如上所示)。

是否有一个 python 模块,我可以期望从中获得复值函数的标准主值(即遵循主分支切割)? 或者,在这一点上,标准本金值的概念是否过于流动,无法预期一致性?


Mar*_*son 4

cmath行为在某种程度上是标准的,因为它不仅限于cmathNumPy:它还符合 C 标准(至少 C99 及更高版本)的附录 G 中推荐的行为,以及 William Kahan 在他的论文“Branch Cuts for Complex Elementary Functions”,副标题为“Much Ado About Nothing's Sign Bit”。

但我们在这里真正看到的是纯数学世界和浮点算术世界之间的另一个分歧。

上述“标准”行为特定于使用浮点算术执行的数学,特别是使用浮点算术格式,其中存在与“正零”不同(等于但不同)的“负零”值。这包括目前几乎无处不在的 IEEE 754 浮点标准。

分支切割与cmath“标准”数学分支匹配(例如,对于asin,我们沿着正实轴从1到无穷大进行切割,并沿着负实轴从-1到负无穷大进行切割),并且像往常一样,子区间上的值[-1, 1]实数线与asin标准数学和cmath模中常见的实数函数相匹配。鉴于此,远离分支切割的连续性迫使除了可能分支切割上的cmath.asin任何地方都与标准数学定义一致。

从数学上讲,要扩展asin到分支切割,您需要选择每次切割是“从上连续”还是“从下连续”,通常的选择asin是从下连续[1, inf)和从上连续(-inf, -1],这将给出您asin(10)期望的第四象限结果。但是,如果您使用 IEEE 754 浮点,则会出现另一个选项:在 的分支切割上asin,参数的虚部始终为零。现在,您可以使用零的符号来确定您将参数解释为位于分支切割的哪一侧。所以我们得到例如:

>>> from cmath import asin
>>> asin(complex(10.0, 0.0))  # 'top' of the branch cut
(1.5707963267948966+2.993222846126381j)
>>> asin(complex(10.0, -0.0))  # 'bottom' of the branch cut
(1.5707963267948966-2.993222846126381j)
Run Code Online (Sandbox Code Playgroud)

这类似于atan2大多数语言中实值函数的工作方式:通常,atan2(0.0, -1.0)被定义为pi,而atan2(-0.0, -1.0)被定义为-pi;零的符号是用来区分的。从数学上来说,这有点作弊,但它在浮点领域有一些很好的特性。例如,我们得到 可以与for allasin(z.conjugate())互换,包括所有浮点特殊情况。象限上的行为是明确定义的(如果您再次使用相关的零的符号来确定每个象限的成员资格)。asin(z).conjugate()z

至于你关于提供“标准”值的Python模块的问题,我不知道有什么,尽管@hpaulj在评论中提到了SymPy。或者您可以伪造零符号以强制以正确的方式解释值。