我需要在仅有正弦函数、余弦函数和基本固定小数点算术(没有浮点数)的环境中实现asin、acos和atan。
我已经有了一个相当好的平方根函数。
我能否使用这些工具来实现相对高效的反三角函数?
我不需要太高的精度(浮点数精度非常有限),基本逼近即可。
我已经最终决定采用表格查找方法,但我想知道是否有更简洁的选择(不需要编写几百行代码来实现基本的数学计算)。
编辑:
为了澄清事情:我需要在每秒35帧的情况下运行该函数数百次。
我需要在仅有正弦函数、余弦函数和基本固定小数点算术(没有浮点数)的环境中实现asin、acos和atan。
我已经有了一个相当好的平方根函数。
我能否使用这些工具来实现相对高效的反三角函数?
我不需要太高的精度(浮点数精度非常有限),基本逼近即可。
我已经最终决定采用表格查找方法,但我想知道是否有更简洁的选择(不需要编写几百行代码来实现基本的数学计算)。
编辑:
为了澄清事情:我需要在每秒35帧的情况下运行该函数数百次。
asin(x) = atan2 (x, sqrt ((1.0 + x) * (1.0 - x)))
acos(x) = atan2 (sqrt ((1.0 + x) * (1.0 - x)), x)
事实证明,在双倍精度(double)上找到有用的CORDIC迭代描述来计算 atan2() 比我想象的更难。下面的网站似乎包含了足够详细的描述,并讨论了两种备选方法,即多项式逼近和查找表:
http://ch.mathworks.com/examples/matlab-fixed-point-designer/615-calculate-fixed-point-arctangent
normalized_atan(x) ~ (b x + x^2) / (1 + 2 b x + x^2)
where b = 0.596227
#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度。
arcsin(z) = Sigma((2n!)/((2^2n)*(n!)^2)*((z^(2n+1))/(2n+1)))
其中n在[0,无穷大)。http://en.wikipedia.org/wiki/Inverse_trigonometric_functions#Expression_as_definite_integrals
您可以使用您的平方根函数进行数值积分,通过无限级数逼近:您是否需要对 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))
是已知的。
我在这个类似的问题中提交我的答案。
nVidia拥有一些我用于自己的工具,例如:acos、asin、atan2等等...
这些算法产生足够精确的结果。以下是一个直接使用它们代码的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即可得到以度为单位的结果,这也是他们“度数”公式的一部分。
也许可以采用类似牛顿-拉普森法的智能暴力方法。
因此,为了解决asin()问题,您可以在sin()上使用最陡下降法。
只有连续函数可以用多项式逼近。而arcsin(x)在点x=1处不连续,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),则找到cos(x))。arctangent函数仅适用于x<1。arctan(x)=pi/2-arctan(1/x)。