[英]Calculating inverse trigonometric functions with formulas
我一直在嘗試創建自定義計算器來計算三角函數。 除了Chebyshev pylonomials和/或Cordic算法,我使用了泰勒系列,它已經精確到幾個小數位。
這是我創建的計算簡單三角函數而沒有任何模塊:
from __future__ import division
def sqrt(n):
ans = n ** 0.5
return ans
def factorial(n):
k = 1
for i in range(1, n+1):
k = i * k
return k
def sin(d):
pi = 3.14159265359
n = 180 / int(d) # 180 degrees = pi radians
x = pi / n # Converting degrees to radians
ans = x - ( x ** 3 / factorial(3) ) + ( x ** 5 / factorial(5) ) - ( x ** 7 / factorial(7) ) + ( x ** 9 / factorial(9) )
return ans
def cos(d):
pi = 3.14159265359
n = 180 / int(d)
x = pi / n
ans = 1 - ( x ** 2 / factorial(2) ) + ( x ** 4 / factorial(4) ) - ( x ** 6 / factorial(6) ) + ( x ** 8 / factorial(8) )
return ans
def tan(d):
ans = sin(d) / sqrt(1 - sin(d) ** 2)
return ans
不幸的是,我找不到任何可以幫助我解釋Python的反三角函數公式的資源。 我也嘗試將sin(x)置於-1( sin(x) ** -1
)的冪,但是沒有按預期工作。
什么是在Python中做到這一點的最佳解決方案(在最好的情況下,我的意思是最簡單的,具有與泰勒系列相似的精度)? 這是可能的電源系列或我需要使用cordic算法?
問題的范圍很廣,但這里有一些簡單的想法(和代碼!)可以作為計算arctan
的起點。 首先,好老泰勒系列。 為簡單起見,我們使用固定數量的術語; 實際上,您可能希望根據x
的大小來決定動態使用的術語數,或者引入某種收斂准則。 使用固定數量的術語,我們可以使用類似於Horner方案的方法進行有效評估。
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).
"""
# Uses Horner's method for evaluation.
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
,無論多少項(或者多少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。 這里的想法是你一勞永逸地預先計算一個1
1/2,
1/4,
等的arctan值表,然后使用它們來計算一般的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
執行任何反函數的最簡單方法是使用二進制搜索。
定義
讓我們假設功能
x = g(y)
我們想要反編碼:
y = f(x) = f(g(y)) x = <x0,x1> y = <y0,y1>
bin搜索浮動
您可以在整數數學中訪問尾數位,如下所示:
但是如果你在計算之前不知道結果的指數,那么你也需要使用浮點數進行bin搜索。
所以二進制搜索背后的想法是將y
尾數從y1
改為y0
,從MSB到LSB逐位改變。 然后調用直接函數g(y)
,如果結果交叉x
返回最后一位更改。
在使用浮點數的情況下,您可以使用將保持尾數位的近似值而不是整數位訪問的變量。 這將消除未知的指數問題。 所以在開始時將y = y0
和實際位設置為MSB值,因此b=(y1-y0)/2
。 在每次迭代后將其減半,並進行與尾數位n
一樣多的迭代...這樣,您可以在(y1-y0)/2^n
精度內獲得n
次迭代的結果。
如果您的反函數不是單調的,則將其分解為單調區間並將其作為單獨的二元搜索處理。
函數增加/減少僅確定交叉條件方向(使用<
或>
)。
C ++ acos示例
所以y = acos(x)
是在x = <-1,+1> , y = <0,M_PI>
,因此減少:
double f64_acos(double x)
{
const int n=52; // mantisa bits
double y,y0,b;
int i;
// handle domain error
if (x<-1.0) return 0;
if (x>+1.0) return 0;
// x = <-1,+1> , y = <0,M_PI> , decreasing
for (y= 0.0,b=0.5*M_PI,i=0;i<n;i++,b*=0.5) // y is min, b is half of max and halving each iteration
{
y0=y; // remember original y
y+=b; // try set "bit"
if (cos(y)<x) y=y0; // if result cross x return to original y decreasing is < and increasing is >
}
return y;
}
我測試了這樣:
double x0,x1,y;
for (x0=0.0;x0<M_PI;x0+=M_PI*0.01) // cycle all angle range <0,M_PI>
{
y=cos(x0); // direct function (from math.h)
x1=f64_acos(y); // my inverse function
if (fabs(x1-x0)>1e-9) // check result and output to log if error
Form1->mm_log->Lines->Add(AnsiString().sprintf("acos(%8.3lf) = %8.3lf != %8.3lf",y,x0,x1));
}
沒有發現任何差異......所以實施工作正常。 在52位尾數上的粗二進制搜索通常比多項式近似慢...另一方面,實現是如此簡單...
[筆記]
如果你不想照顧單調的間隔,你可以嘗試
當您處理測角函數時,您需要處理奇點以避免NaN
或除零等...
如果你對這里感興趣的更多bin搜索示例(主要是整數)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.