簡體   English   中英

用公式計算反三角函數

[英]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

執行任何反函數的最簡單方法是使用二進制搜索。

  1. 定義

    讓我們假設功能

     x = g(y) 

    我們想要反編碼:

     y = f(x) = f(g(y)) x = <x0,x1> y = <y0,y1> 
  2. bin搜索浮動

    您可以在整數數學中訪問尾數位,如下所示:

    但是如果你在計算之前不知道結果的指數,那么你也需要使用浮點數進行bin搜索。

    所以二進制搜索背后的想法是將y尾數從y1改為y0 ,從MSBLSB逐位改變。 然后調用直接函數g(y) ,如果結果交叉x返回最后一位更改。

    在使用浮點數的情況下,您可以使用將保持尾數位的近似值而不是整數位訪問的變量。 這將消除未知的指數問題。 所以在開始時將y = y0和實際位設置為MSB值,因此b=(y1-y0)/2 在每次迭代后將其減半,並進行與尾數位n一樣多的迭代...這樣,您可以在(y1-y0)/2^n精度內獲得n次迭代的結果。

    如果您的反函數不是單調的,則將其分解為單調區間並將其作為單獨的二元搜索處理。

    函數增加/減少僅確定交叉條件方向(使用<> )。

C ++ acos示例

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM