用公式计算反三角函数

时间:2017-05-29 19:09:32

标签: python math trigonometry

我一直在尝试创建用于计算三角函数的自定义计算器。除了Chebyshev pylonomials和/或Cordic算法,我使用了泰勒系列,它已经精确到几个小数位。

这是我在没有任何模块的情况下计算简单三角函数所创建的:

class Component 
{
   public:
      virtual ~Component() {}
};

#include <map>

// Implement GameObject with no knowledge of any of the classes derived 
// from Component. That will be one sure fire way of making sure that it can
// be used for any class derived from Component.

class GameObject
{
   public:

    //Add functionality
    template <class T>
    T& AddComponent()
    {
       T* obj = new T{};
       AddComponent(getTypeID<T>(), obj);
       return *obj;
    }

    //Get functionality
    template <typename T>
    T& GetComponent()
    {
       T* obj = dynamic_cast<T*>(GetComponent(getTypeID<T>()));
       return *obj;
    }

   private:

    // If necessary, the implementation can be moved to a .cpp file.
    void AddComponent(int typeID, Component* obj)
    {
       components[typeID] = obj;
    }

    // If necessary, the implementation can be moved to a .cpp file.
    Component* GetComponent(int typeID)
    {
       return components[typeID];
    }

    // Key function.
    // It gets a unique integer for every type.
    template <class T>
       static int getTypeID()
       {
          static int typeID = getNextID();
          return typeID;
       }

    // If necessary, the implementation can be moved to a .cpp file.
    static int getNextID()
    {
       static int nextID = 0;
       return ++nextID;
    }

    std::map<int, Component*> components;
};

class Collider : public Component
{
};

class SphereCollider :public Collider
{
};

int main()
{
   GameObject go;
   go.AddComponent<Collider>();
   go.AddComponent<SphereCollider>();

   Collider c = go.GetComponent<Collider>();
   SphereCollider sc = go.GetComponent<SphereCollider>();
}

不幸的是,我找不到任何可以帮助我解释Python的反三角函数公式的资源。我也尝试将sin(x)置于-1(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中做到这一点的最佳解决方案是什么(在最好的情况下,我的意思是最简单的,具有与泰勒系列相似的精度)?这是可能的电源系列还是我需要使用cordic算法?

2 个答案:

答案 0 :(得分:2)

问题的范围很广,但这里有一些简单的想法(和代码!)可以作为计算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变大,精度下降,{{1}无论我们抛出多少个术语(或多少精确度),该系列都不会收敛。因此,我们需要一种更好的方法来计算更大的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的解y。由于arctan已经自然地位于区间tan(y) = x中,因此二分搜索效果很好:

(-pi/2, pi/2)

最后,只是为了好玩,这是一个类似CORDIC的实现,它更适合低级实现而不是Python。这里的想法是,您一劳永逸地预先计算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) 1 1/2,等的arctan值表,然后使用这些值来计算一般arctan值,主要是计算真实角度的逐次逼近。值得注意的是,在预计算步骤之后,arctan计算仅涉及2的幂的加法,减法和乘法。(当然,这些乘法并不比Python级别的任何其他乘法更有效,但更接近硬件,这可能会产生很大的不同。)

1/4,

上述每种方法都有其优点和缺点,所有上述代码都可以通过多种方式得到改进。我鼓励你进行实验和探索。

要总结一下,以下是在少量非常精心挑选的测试值上调用上述函数的结果,与标准库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 :(得分:1)

执行任何反函数的最简单方法是使用二进制搜索。

  1. <强>定义

    让假设功能

    x = g(y)
    

    我们想要编码它的逆码:

    y = f(x) = f(g(y))
    x = <x0,x1>
    y = <y0,y1>
    
  2. 在浮动广告上搜索

    你可以在整数数学中访问尾数位,如下所示:

    但是如果你在计算之前不知道结果的指数,那么你也需要使用浮点数进行bin搜索。

    所以二进制搜索背后的想法是将y的尾数从y1改为y0 MSB LSB 。然后调用直接函数g(y),如果结果交叉x,则返回最后一位更改。

    在使用浮点数的情况下,您可以使用将保持尾数位的近似值而不是整数位访问的变量。这将消除未知的指数问题。因此,在开始时将y = y0和实际位设置为 MSB ,以便b=(y1-y0)/2。在每次迭代后将其减半并执行与尾数位n一样多的迭代...这样,您可以在n精度内获得(y1-y0)/2^n次迭代的结果。

    如果您的反函数不是单调的,则将其分解为单调区间并将其作为单独的二元搜索处理。

    函数增加/减少只是确定交叉条件方向(使用<>)。

  3. 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搜索示例(主要是整数)