下面的代码提供了
sin()
和
acos()
的简单实现,可以满足您的精度要求并且您可能想要尝试。请注意,您平台上的数学库实现很可能高度针对该平台的特定硬件功能进行了优化,并且可能也是以汇编代码编写的,以达到最大效率,因此即使精度要求从完全双精度放宽,简单的编译C代码不考虑硬件的具体情况也不太可能提供更高的性能。正如Viktor Latypov指出的那样,还值得寻找不需要调用超越函数的算法替代方案。
在下面的代码中,我尽量使用简单、可移植的结构。如果你的编译器支持C99和C++11指定的
rint()
函数,你可能想使用它来代替
my_rint()
。在某些平台上,调用
floor()
函数可能会很昂贵,因为它需要动态改变机器状态。函数
my_rint()
、
sin_core()
、
cos_core()
和
asin_core()
最好是内联以获得最佳性能。你的编译器可能会在高优化级别下(例如使用
-O3
编译时)自动执行这些操作,或者你可以为这些函数添加适当的内联属性,例如inline或__inline,具体取决于你的工具链。
不知道你的平台情况,我选择了简单的多项式逼近,这些逼近使用Estrin方案加上Horner方案进行评估。请参见Wikipedia对这些评估方案的描述:
http://en.wikipedia.org/wiki/Estrin%27s_scheme,
http://en.wikipedia.org/wiki/Horner_scheme。
这些近似值本身属于极小极大类型,并使用Remez算法进行自定义生成,具体信息请参见以下链接:
http://en.wikipedia.org/wiki/Minimax_approximation_algorithm ,http://en.wikipedia.org/wiki/Remez_algorithm
对于
acos()
的参数化简中所用的恒等式已在注释中标注;对于
sin()
,我使用了Cody/Waite风格的参数化简方法,详见以下书籍:W. J. Cody, W. Waite, Software Manual for the Elementary Functions. Prentice-Hall, 1980
注释中提到的误差界限是近似值,未经过严格测试或证明。
double my_rint (double x)
{
double t = floor (fabs(x) + 0.5);
return (x < 0.0) ? -t : t;
}
double cos_core (double x)
{
double x8, x4, x2;
x2 = x * x;
x4 = x2 * x2;
x8 = x4 * x4;
return (-2.7236370439787708e-7 * x2 + 2.4799852696610628e-5) * x8 +
(-1.3888885054799695e-3 * x2 + 4.1666666636943683e-2) * x4 +
(-4.9999999999963024e-1 * x2 + 1.0000000000000000e+0);
}
double sin_core (double x)
{
double x4, x2, t;
x2 = x * x;
x4 = x2 * x2;
return ((2.7181216275479732e-6 * x2 - 1.9839312269456257e-4) * x4 +
(8.3333293048425631e-3 * x2 - 1.6666666640797048e-1)) * x2 * x + x;
}
double asin_core (double x)
{
double x8, x4, x2;
x2 = x * x;
x4 = x2 * x2;
x8 = x4 * x4;
return (((4.5334220547132049e-2 * x2 - 1.1226216762576600e-2) * x4 +
(2.6334281471361822e-2 * x2 + 2.0596336163223834e-2)) * x8 +
(3.0582043602875735e-2 * x2 + 4.4630538556294605e-2) * x4 +
(7.5000364034134126e-2 * x2 + 1.6666666300567365e-1)) * x2 * x + x;
}
double my_sin (double x)
{
double q, t;
int quadrant;
q = my_rint (x * 6.3661977236758138e-1);
quadrant = (int)q;
t = x - q * 1.5707963267923333e+00;
t = t - q * 2.5633441515945189e-12;
if (quadrant & 1) {
t = cos_core(t);
} else {
t = sin_core(t);
}
return (quadrant & 2) ? -t : t;
}
double my_acos (double x)
{
double xa, t;
xa = fabs (x);
if (xa > 0.5625) {
t = 2.0 * asin_core (sqrt (0.5 * (1.0 - xa)));
} else {
t = 1.5707963267948966 - asin_core (xa);
}
return (x < 0.0) ? (3.1415926535897932 - t) : t;
}
1e-10
,你只有四个数量级的差距与double
精度。我认为你可能无法在性能和准确度上得到足够的近似,但我可能是错误的。 - Jonas Schäfer