简体   繁体   English

用公式计算反三角函数

[英]Calculating inverse trigonometric functions with formulas

I have been trying to create custom calculator for calculating trigonometric functions. 我一直在尝试创建自定义计算器来计算三角函数。 Aside from Chebyshev pylonomials and/or Cordic algorithm I have used Taylor series which have been accurate by few places of decimal. 除了Chebyshev pylonomials和/或Cordic算法,我使用了泰勒系列,它已经精确到几个小数位。

This is what i have created to calculate simple trigonometric functions without any modules: 这是我创建的计算简单三角函数而没有任何模块:

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 

Unfortunately i could not find any sources that would help me interpret inverse trigonometric function formulas for Python. 不幸的是,我找不到任何可以帮助我解释Python的反三角函数公式的资源。 I have also tried putting sin(x) to the power of -1 ( sin(x) ** -1 ) which didn't work as expected. 我也尝试将sin(x)置于-1( sin(x) ** -1 )的幂,但是没有按预期工作。

What could be the best solution to do this in Python (In the best, I mean simplest with similar accuracy as Taylor series)? 什么是在Python中做到这一点的最佳解决方案(在最好的情况下,我的意思是最简单的,具有与泰勒系列相似的精度)? Is this possible with power series or do i need to use cordic algorithm? 这是可能的电源系列或我需要使用cordic算法?

The question is broad in scope, but here are some simple ideas (and code!) that might serve as a starting point for computing arctan . 问题的范围很广,但这里有一些简单的想法(和代码!)可以作为计算arctan的起点。 First, the good old Taylor series. 首先,好老泰勒系列。 For simplicity, we use a fixed number of terms; 为简单起见,我们使用固定数量的术语; in practice, you might want to decide the number of terms to use dynamically based on the size of x , or introduce some kind of convergence criterion. 实际上,您可能希望根据x的大小来决定动态使用的术语数,或者引入某种收敛准则。 With a fixed number of terms, we can evaluate efficiently using something akin to Horner's scheme. 使用固定数量的术语,我们可以使用类似于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

The above code gives good results for small x (say smaller than 0.1 in absolute value), but the accuracy drops off as x becomes larger, and for abs(x) > 1.0 , the series never converges, no matter how many terms (or how much extra precision) we throw at it. 上面的代码给出了小x (比如绝对值小于0.1 )的良好结果,但随着x变大,精度下降,而对于abs(x) > 1.0 ,无论多少项(或者多少abs(x) > 1.0 ,系列都不会收敛(或多少精确度我们抛出它。 So we need a better way to compute for larger x . 所以我们需要一种更好的方法来计算更大的x One solution is to use argument reduction, via the identity arctan(x) = 2 * arctan(x / (1 + sqrt(1 + x^2))) . 一种解决方案是通过身份arctan(x) = 2 * arctan(x / (1 + sqrt(1 + x^2)))使用参数减少。 This gives the following code, which builds on arctan_taylor to give reasonable results for a wide range of x (but beware possible overflow and underflow when computing x*x ). 这给出了以下代码,它构建在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

Alternatively, given an existing implementation for tan , you could simply find a solution y to the equation tan(y) = x using traditional root-finding methods. 或者,给定tan的现有实现,您可以使用传统的根查找方法简单地找到方程tan(y) = x的解y Since arctan is already naturally bounded to lie in the interval (-pi/2, pi/2) , bisection search works well: 由于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)

Finally, just for fun, here's a CORDIC-like implementation, which is really more appropriate for a low-level implementation than for Python. 最后,只是为了好玩,这里是一个类似CORDIC的实现,它更适合低级实现而不是Python。 The idea here is that you precompute, once and for all, a table of arctan values for 1 , 1/2, 1/4, etc., and then use those to compute general arctan values, essentially by computing successive approximations to the true angle. 这里的想法是你一劳永逸地预先计算一个1 1/2, 1/4,等的arctan值表,然后使用它们来计算一般的arctan值,主要是通过计算对真值的连续近似值角度。 The remarkable part is that, after the precomputation step, the arctan computation involves only additions, subtractions, and multiplications by by powers of 2. (Of course, those multiplications aren't any more efficient than any other multiplication at the level of Python, but closer to the hardware, this could potentially make a big difference.) 值得注意的是,在预计算步骤之后,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

Each of the above methods has its strengths and weaknesses, and all of the above code can be improved in a myriad of ways. 上述每种方法都有其优点和缺点,所有上述代码都可以通过多种方式得到改进。 I encourage you to experiment and explore. 我鼓励你去尝试和探索。

To wrap it all up, here are the results of calling the above functions on a small number of not-very-carefully-chosen test values, comparing with the output of the standard library math.atan function: 为了总结这一点,以下是在少量非常精心挑选的测试值上调用上述函数的结果,与标准库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),
    ))

Output on my machine: 我机器上的输出:

    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

The simplest way to do any inverse function is to use binary search. 执行任何反函数的最简单方法是使用二进制搜索。

  1. definitions 定义

    let assume function 让我们假设功能

     x = g(y) 

    And we want to code its inverse: 我们想要反编码:

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

    You can do it on integer math accessing mantissa bits like in here: 您可以在整数数学中访问尾数位,如下所示:

    but if you do not know the exponent of the result prior to computation then you need to use floats for bin search too. 但是如果你在计算之前不知道结果的指数,那么你也需要使用浮点数进行bin搜索。

    so the idea behind binary search is to change mantissa of y from y1 to y0 bit by bit from MSB to LSB . 所以二进制搜索背后的想法是将y尾数从y1改为y0 ,从MSBLSB逐位改变。 Then call direct function g(y) and if the result cross x revert the last bit change. 然后调用直接函数g(y) ,如果结果交叉x返回最后一位更改。

    In case of using floats you can use variable that will hold approximate value of the mantissa bit targeted instead of integer bit access. 在使用浮点数的情况下,您可以使用将保持尾数位的近似值而不是整数位访问的变量。 That will eliminate unknown exponent problem. 这将消除未知的指数问题。 So at the beginning set y = y0 and actual bit to MSB value so b=(y1-y0)/2 . 所以在开始时将y = y0和实际位设置为MSB值,因此b=(y1-y0)/2 After each iteration halve it and do as many iterations as you got mantissa bits n ... This way you obtain result in n iterations within (y1-y0)/2^n accuracy. 在每次迭代后将其减半,并进行与尾数位n一样多的迭代...这样,您可以在(y1-y0)/2^n精度内获得n次迭代的结果。

    If your inverse function is not monotonic break it into monotonic intervals and handle each as separate binary search. 如果您的反函数不是单调的,则将其分解为单调区间并将其作为单独的二元搜索处理。

    The function increasing/decreasing just determine the crossing condition direction (use of < or > ). 函数增加/减少仅确定交叉条件方向(使用<> )。

C++ acos example C ++ acos示例

ACOS

so y = acos(x) is defined on x = <-1,+1> , y = <0,M_PI> and decreasing so: 所以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;
    }

I tested it like this: 我测试了这样:

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));
    }

Without any difference found... so the implementation is working correctly. 没有发现任何差异......所以实施工作正常。 Of coarse binary search on 52 bit mantissa is usually slower then polynomial approximation ... on the other hand the implementation is so simple ... 在52位尾数上的粗二进制搜索通常比多项式近似慢...另一方面,实现是如此简单...

[Notes] [笔记]

If you do not want to take care of the monotonic intervals you can try 如果你不想照顾单调的间隔,你可以尝试

As you are dealing with goniometric functions you need to handle singularities to avoid NaN or division by zero etc ... 当您处理测角函数时,您需要处理奇点以避免NaN或除零等...

If you're interested here more bin search examples (mostly on integers) 如果你对这里感兴趣的更多bin搜索示例(主要是整数)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM