近似反三角函数

Posted

技术标签:

【中文标题】近似反三角函数【英文标题】:Approximating inverse trigonometric functions 【发布时间】:2011-09-11 12:32:38 【问题描述】:

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

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

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

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

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

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

编辑:

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

【问题讨论】:

How do Trigonometric functions work? 的可能重复项 建议的副本更多地是关于三角函数的工作原理(就像它的标题一样)。这是关于反三角函数的。 【参考方案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

【讨论】:

以上是关于近似反三角函数的主要内容,如果未能解决你的问题,请参考以下文章

数学笔记6——线性近似和二阶近似

怎样计算定积分的近似值?

(十三)从零开始学人工智能-强化学习:值函数近似和策略梯度

强化学习(David Silver)6:值函数近似

习题5-7 使用函数求余弦函数的近似值 (15 分)

[PTA]习题5-7 使用函数求余弦函数的近似值