【问题标题】:Approximating inverse trigonometric functions近似反三角函数
【发布时间】:2023-03-05 09:01:01
【问题描述】:

我必须在只有以下数学工具的环境中实现 asin、acos 和 atan:

  • 正弦
  • 余弦
  • 基本定点算术(浮点数不可用)

我也已经有了相当好的平方根函数。

我可以使用它们来实现相当有效的反三角函数吗?

我不需要太大的精度(无论如何,浮点数的精度非常有限),基本的近似值就可以了。

我已经决定使用表查找,但我想知道是否有更简洁的选项(不需要几百行代码就可以实现基本数学)。

编辑:

搞清楚:我需要以每秒 35 帧的速度每帧运行数百次函数。

【问题讨论】:

  • 建议的副本更多地是关于三角函数的工作原理(就像它的标题一样)。这是关于反三角函数的。

标签: algorithm math trigonometry


【解决方案1】:

在定点环境(S15.16)中,我成功地使用了 CORDIC 算法(有关一般描述,请参见 Wikipedia)来计算 atan2(y,x),然后从中导出 asin() 和 acos() - 涉及平方根的已知函数恒等式:

asin(x) = atan2 (x, sqrt ((1.0 + x) * (1.0 - x)))
acos(x) = atan2 (sqrt ((1.0 + x) * (1.0 - x)), x)

事实证明,在 double 上为 atan2() 找到 CORDIC 迭代的有用描述比我想象的要难。以下网站似乎包含足够详细的描述,并且还讨论了两种替代方法,多项式逼近和查找表:

http://ch.mathworks.com/examples/matlab-fixed-point-designer/615-calculate-fixed-point-arctangent

【讨论】:

  • 来自维基百科,CORDIC 甚至不使用三角函数(简洁!);我想你所做的是搜索;给定 sin(), cos() 似乎 Newton-Raphson 或类似的会更好? (需要更少的迭代,尽管迭代的成本会有所不同。)
  • 我建议研究 CORDIC 的原因是因为它只需要定点算术。 CORDIC 最常见的用途可能是实现 sin / cos,这就是我第一次了解它的方式(1987 年)。但是也可以使用 CORDIC 计算很多其他函数,例如 atan2。由于我没有任何代码用于使用 CORDIC 计算 atan2,因此我试图找到一个具有足够详细信息的网站,以便有人可以基于它来实现。我在上面发布的链接是我在几分钟内通过搜索引擎找到的最佳页面。
【解决方案2】:

arcsin(x) 函数需要较大的精度吗?如果不是,您可以在 N 个节点中计算 arcsin,并将值保存在内存中。我建议使用线近似。如果x = A*x_(N) + (1-A)*x_(N+1)x = A*arcsin(x_(N)) + (1-A)*arcsin(x_(N+1)) 其中arcsin(x_(N)) 是已知的。

【讨论】:

  • 是的,这就是我在 OP 中所说的表查找。看不出我为什么会在运行时计算出的原因,我会将这些值直接烘焙到程序中,所以实际的 asin 计算只是两个值之间的插值。
【解决方案3】:

您可能想要使用近似值:使用 infinite series 直到解决方案对您来说足够接近。

例如:
arcsin(z) = Sigma((2n!)/((2^2n)*(n!)^2)*((z^(2n+1))/(2n+1))) where n in [0,infinity)

【讨论】:

    【解决方案4】:

    http://en.wikipedia.org/wiki/Inverse_trigonometric_functions#Expression_as_definite_integrals

    您可以使用平方根函数进行数值积分,近似于无限级数:

    【讨论】:

    • 我刚刚从维基百科复制了图片。
    【解决方案5】:

    将以下代码添加到定点应该很容易。它使用rational approximation 来计算归一化为 [0 1) 区间的反正切(您可以将其乘以 Pi/2 以获得真正的反正切)。然后,您可以使用well known identities 从反正切中获取 arcsin/arccos。

    normalized_atan(x) ~ (b x + x^2) / (1 + 2 b x + x^2)
    
    where b = 0.596227
    

    最大误差为0.1620º

    #include <stdint.h>
    #include <math.h>
    
    // Approximates atan(x) normalized to the [-1,1] range
    // with a maximum error of 0.1620 degrees.
    
    float norm_atan( float x )
    {
        static const uint32_t sign_mask = 0x80000000;
        static const float b = 0.596227f;
    
        // Extract the sign bit
        uint32_t ux_s  = sign_mask & (uint32_t &)x;
    
        // Calculate the arctangent in the first quadrant
        float bx_a = ::fabs( b * x );
        float num = bx_a + x * x;
        float atan_1q = num / ( 1.f + bx_a + num );
    
        // Restore the sign bit
        uint32_t atan_2q = ux_s | (uint32_t &)atan_1q;
        return (float &)atan_2q;
    }
    
    // Approximates atan2(y, x) normalized to the [0,4) range
    // with a maximum error of 0.1620 degrees
    
    float norm_atan2( float y, float x )
    {
        static const uint32_t sign_mask = 0x80000000;
        static const float b = 0.596227f;
    
        // Extract the sign bits
        uint32_t ux_s  = sign_mask & (uint32_t &)x;
        uint32_t uy_s  = sign_mask & (uint32_t &)y;
    
        // Determine the quadrant offset
        float q = (float)( ( ~ux_s & uy_s ) >> 29 | ux_s >> 30 ); 
    
        // Calculate the arctangent in the first quadrant
        float bxy_a = ::fabs( b * x * y );
        float num = bxy_a + y * y;
        float atan_1q =  num / ( x * x + bxy_a + num );
    
        // Translate it to the proper quadrant
        uint32_t uatan_2q = (ux_s ^ uy_s) | (uint32_t &)atan_1q;
        return q + (float &)uatan_2q;
    } 
    

    如果您需要更高的精度,可以使用三阶有理函数:

    normalized_atan(x) ~ ( c x + x^2 + x^3) / ( 1 + (c + 1) x + (c + 1) x^2 + x^3)
    
    where c = (1 + sqrt(17)) / 8
    

    最大近似误差为 0.00811º

    【讨论】:

      【解决方案6】:

      在这里提交我来自other similar question.的答案

      nVidia 有一些很棒的资源,我自己用过,几个例子:acosasinatan2 等等...

      这些算法产生足够精确的结果。这是一个直接的 Python 示例,其中粘贴了他们的代码副本:

      import math
      def nVidia_acos(x):
          negate = float(x<0)
          x=abs(x)
          ret = -0.0187293
          ret = ret * x
          ret = ret + 0.0742610
          ret = ret * x
          ret = ret - 0.2121144
          ret = ret * x
          ret = ret + 1.5707288
          ret = ret * math.sqrt(1.0-x)
          ret = ret - 2 * negate * ret
          return negate * 3.14159265358979 + ret
      

      以下是比较结果:

      nVidia_acos(0.5)  result: 1.0471513828611643
      math.acos(0.5)    result: 1.0471975511965976
      

      这很接近!乘以 57.29577951 得到度数的结果,这也是他们的“度数”公式。

      【讨论】:

        【解决方案7】:

        也许是某种聪明的蛮力,比如牛顿拉普森。

        因此,为了解决 asin(),你需要在 sin() 上进行最陡峭的下降

        【讨论】:

        • 您可以从一个小的查找表中选择起点以加快计算速度。
        【解决方案8】:

        使用多项式近似。最小二乘拟合最简单(Microsoft Excel 有),切比雪夫近似更准确。

        这个问题之前已经讨论过:How do Trigonometric functions work?

        【讨论】:

          【解决方案9】:

          只有连续函数才能被多项式逼近。并且 arcsin(x) 在点 x=1.same arccos(x) 是不连续的。但是在这种情况下将范围缩小到区间 1,sqrt(1/2) 可以避免这种情况。我们有 arcsin(x)=pi/2- arccos(x),arccos(x)=pi/2-arcsin(x)。您可以使用 matlab 进行极小极大近似。仅在 [0,sqrt(1/2) 范围内近似)](如果该 arcsin 的角度请求大于 sqrt(1/2) 仅针对 x

          【讨论】:

            猜你喜欢
            • 2021-02-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-05-12
            相关资源
            最近更新 更多