这个问题的范围很广,但是以下是一些简单的想法(和代码!)可能会作为计算arctan
的起点。首先是老生常谈的泰勒级数。为了简单起见,我们使用固定数量的项;在实践中,您可能希望根据x
的大小动态决定要使用的项数,或者引入某种收敛准则。有了固定数量的项,我们可以使用类似于霍纳方案的东西高效地评估。
def arctan_taylor(x, terms=9):
"""
Compute arctan for small x via Taylor polynomials.
Uses a fixed number of terms. The default of 9 should give good results for
abs(x) < 0.1. Results will become poorer as abs(x) increases, becoming
unusable as abs(x) approaches 1.0 (the radius of convergence of the
series).
"""
t = 0.0
for n in range(2*terms-1, 0, -2):
t = 1.0/n - x*x*t
return x * t
上面的代码对于小的
x
(比如绝对值小于
0.1
)可以得到良好的结果,但是随着
x
的增大,精度会下降,并且对于
abs(x) > 1.0
,无论我们投入多少项(或多少额外精度),级数都不会收敛。因此,我们需要一种更好的方法来计算较大的
x
。一种解决方案是通过参数归约,使用恒等式
arctan(x) = 2 * arctan(x / (1 + sqrt(1 + x^2)))
。这给出了以下代码,它建立在
arctan_taylor
的基础上,可以为广泛范围的
x
给出合理的结果(但要注意在计算
x*x
时可能会发生溢出和下溢)。
import math
def arctan_taylor_with_reduction(x, terms=9, threshold=0.1):
"""
Compute arctan via argument reduction and Taylor series.
Applies reduction steps until x is below `threshold`,
then uses Taylor series.
"""
reductions = 0
while abs(x) > threshold:
x = x / (1 + math.sqrt(1 + x*x))
reductions += 1
return arctan_taylor(x, terms=terms) * 2**reductions
或者,如果已经存在 tan
的实现,则可以使用传统的根查找方法仅仅找到方程式 tan(y) = x
的解 y
。由于 arctan 已经自然地被限制在区间 (-pi/2, pi/2)
中,因此二分查找是有效的:
def arctan_from_tan(x, tolerance=1e-15):
"""
Compute arctan as the inverse of tan, via bisection search. This assumes
that you already have a high quality tan function.
"""
low, high = -0.5 * math.pi, 0.5 * math.pi
while high - low > tolerance:
mid = 0.5 * (low + high)
if math.tan(mid) < x:
low = mid
else:
high = mid
return 0.5 * (low + high)
最后,只是为了好玩,这里提供一种类似CORDIC的实现方式,它更适合于低级别的实现而不是Python。这里的想法是你预先计算一个arctan值表,包含1
、1/2
、1/4
等值,然后使用这些值来计算一般的arctan值,基本上通过计算真实角度的连续逼近来实现。值得注意的是,在预处理步骤之后,arctan计算仅涉及加法、减法和乘以2的幂次方的乘法。(当然,在Python层面上,这些乘法并不比任何其他乘法更有效率,但在接近硬件层面上,这可能会产生很大的差异。)
cordic_table_size = 60
cordic_table = [(2**-i, math.atan(2**-i))
for i in range(cordic_table_size)]
def arctan_cordic(y, x=1.0):
"""
Compute arctan(y/x), assuming x positive, via CORDIC-like method.
"""
r = 0.0
for t, a in cordic_table:
if y < 0:
r, x, y = r - a, x - t*y, y + t*x
else:
r, x, y = r + a, x + t*y, y - t*x
return r
每种方法都有其优点和缺点,所有代码都可以以无数种方式进行改进。我鼓励你去尝试和探索。
总结一下,在一些不太仔细选择的测试值上调用上述函数的结果如下,与标准库math.atan函数的输出进行比较:
test_values = [2.314, 0.0123, -0.56, 168.9]
for value in test_values:
print("{:20.15g} {:20.15g} {:20.15g} {:20.15g}".format(
math.atan(value),
arctan_taylor_with_reduction(value),
arctan_from_tan(value),
arctan_cordic(value),
))
我的机器上的输出:
1.16288340166519 1.16288340166519 1.16288340166519 1.16288340166519
0.0122993797673 0.0122993797673 0.0122993797673002 0.0122993797672999
-0.510488321916776 -0.510488321916776 -0.510488321916776 -0.510488321916776
1.56487573286064 1.56487573286064 1.56487573286064 1.56487573286064
float
),还是需要适用于任意精度?就目前而言,这个问题太广泛了,无法给出一个好的答案。例如,对于arctan,你可以考虑使用幂级数、参数缩减、通过复对数计算、牛顿方法来计算现有tan的倒数、切比雪夫逼近、这些方法的某种组合等等。另外,避免使用math
模块的理由是什么? - Mark Dickinsonx**5/5! = x**3/3! * x**2 / (4*5)
。这样,你甚至不需要阶乘函数。 - Mark Dickinson