jma*_*erx 22 c c++ algorithm math performance
我有自己的,非常快的cos函数:
float sine(float x)
{
const float B = 4/pi;
const float C = -4/(pi*pi);
float y = B * x + C * x * abs(x);
// const float Q = 0.775;
const float P = 0.225;
y = P * (y * abs(y) - y) + y; // Q * y + P * y * abs(y)
return y;
}
float cosine(float x)
{
return sine(x + (pi / 2));
}
Run Code Online (Sandbox Code Playgroud)
但是现在当我描述时,我发现acos()正在杀死处理器.我不需要高度精确.什么是计算acos的快速方法(x)谢谢.
dan*_*n04 39
一个简单的三次近似,x∈{-1,-½,0,½,1}的拉格朗日多项式是:
double acos(x) {
return (-0.69813170079773212 * x * x - 0.87266462599716477) * x + 1.5707963267948966;
}
Run Code Online (Sandbox Code Playgroud)
它的最大误差约为0.18rad.
spe*_*der 22
有多余的记忆?查找表(如果需要,带插值)将是最快的.
Fno*_*ord 16
nVidia有一些很好的资源,展示了如何近似非常昂贵的数学函数,例如:acos asin atan2 等等......
当执行速度比精度更重要(在合理范围内)时,这些算法会产生良好的结果.这是他们的acos功能:
// Absolute error <= 6.7e-5
float acos(float x) {
float negate = float(x < 0);
x = abs(x);
float 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 * sqrt(1.0-x);
ret = ret - 2 * negate * ret;
return negate * 3.14159265358979 + ret;
}
Run Code Online (Sandbox Code Playgroud)
以下是计算acos(0.5)时的结果:
nVidia: result: 1.0471513828611643
math.h: result: 1.0471975511965976
Run Code Online (Sandbox Code Playgroud)
那非常接近!根据您所需的精确度,这可能是一个很好的选择.
我有我自己的.它非常准确,速度很快.它取决于我围绕四次收敛建立的定理.它真的很有趣,你可以看到方程式以及它使我的自然对数近似收敛的速度:https://www.desmos.com/calculator/yb04qt8jx4
这是我的arccos代码:
function acos(x)
local a=1.43+0.59*x a=(a+(2+2*x)/a)/2
local b=1.65-1.41*x b=(b+(2-2*x)/b)/2
local c=0.88-0.77*x c=(c+(2-a)/c)/2
return (8*(c+(2-a)/c)-(b+(2-2*x)/b))/6
end
Run Code Online (Sandbox Code Playgroud)
很多只是平方根近似.它的效果也非常好,除非你太接近于取0的平方根.它的平均误差(不包括x = 0.99到1)为0.0003.但问题是,在0.99时,它开始变得很糟糕,而在x = 1时,精度的差异变为0.05.当然,这可以通过在平方根(lol nope)上进行更多迭代来解决,或者只是一点点,如果x> 0.99然后使用一组不同的平方根线性化,但这会使代码变得冗长而丑陋.
如果你不太关心准确性,那么你可以在每个平方根上进行一次迭代,这仍然可以保持在0.0162范围内的某个位置,或者精度如下:
function acos(x)
local a=1.43+0.59*x a=(a+(2+2*x)/a)/2
local b=1.65-1.41*x b=(b+(2-2*x)/b)/2
local c=0.88-0.77*x c=(c+(2-a)/c)/2
return 8/3*c-b/3
end
Run Code Online (Sandbox Code Playgroud)
如果您对它没问题,可以使用预先存在的平方根代码.它将摆脱在x = 1时有点疯狂的等式:
function acos(x)
local a = math.sqrt(2+2*x)
local b = math.sqrt(2-2*x)
local c = math.sqrt(2-a)
return 8/3*d-b/3
end
Run Code Online (Sandbox Code Playgroud)
坦率地说,如果你真的有时间紧迫,请记住你可以将arccos线性化为3.14159-1.57079x并且只做:
function acos(x)
return 1.57079-1.57079*x
end
Run Code Online (Sandbox Code Playgroud)
无论如何,如果你想查看我的arccos近似方程式列表,你可以访问https://www.desmos.com/calculator/tcaty2sv8l我知道我的近似值对于某些事情来说不是最好的,但如果你'如果我的近似值很有用,请使用它们,但请尽量给予我信任.
您可以使用dan04建议的多项式来近似反余弦,但是多项式是-1和1附近非常差的近似,其中反余弦的导数变为无穷大.当你增加多项式的次数时,你会迅速达到递减收益,并且仍然很难在端点周围得到一个很好的近似值.一个有理函数(两个多项式的商)可以在这个情况下,一个更好的近似.
acos(x) ? ?/2 + (ax + bx³) / (1 + cx² + dx?)
Run Code Online (Sandbox Code Playgroud)
哪里
a = -0.939115566365855
b = 0.9217841528914573
c = -1.2845906244690837
d = 0.295624144969963174
Run Code Online (Sandbox Code Playgroud)
在区间(-1,1)上的最大绝对误差为0.017弧度(0.96度).下面是一个图(黑色的反余弦,红色的三次多项式近似,上面的蓝色函数)用于比较:
选择上述系数以最小化整个域上的最大绝对误差.如果您愿意在端点处允许更大的错误,则可以使间隔(-0.98,0.98)上的错误小得多.度数为5的分子和度数为2的分母大约与上述函数一样快,但稍微不准确.以性能为代价,您可以通过使用更高次多项式来提高精度.
关于性能的注意事项:计算两个多项式仍然非常便宜,您可以使用融合乘法加法指令.划分并不是那么糟糕,因为你可以使用硬件倒数近似和乘法.与acos近似中的误差相比,倒数近似中的误差可以忽略不计.在2.6 GHz Skylake i7上,这种近似可以使用AVX每6个周期执行大约8个反余弦.(即吞吐量,延迟超过6个周期.)
您可以采用的另一种方法是使用复数。根据de Moivre的公式,
?x = cos(π/ 2 * x)+?* sin(π/ 2 * x)
令θ=π/ 2 * x。那么x =2θ/π
您如何计算的幂?没有罪恶和COS?从一个预先计算的表开始,得到2的幂:
计算?的任意值 x,将指数近似为二进制分数,然后将表中的相应值相乘。
例如,要找到72°= 0.8?/ 2的正弦和余弦:
?0.8
≈?205/256
=?0b11001101
=?1/2 *?1/4 *?1/32 *?1/64 *?1/256
= 0.3078496400415349 + 0.9514350209690084 *?
要查找asin和acos,可以将此表与Bisection方法一起使用:
例如,要找到asin(0.6)(3-4-5三角形中的最小角度):
每次增加x时,乘以相应的?幂。每次减少x,除以相应的?幂。
如果在此处停止,我们将获得acos(0.6)≈13/32 *π/ 2 = 0.6381360077604268(“精确”值为0.6435011087932844。)
当然,准确性取决于迭代次数。对于快速的近似值,请使用10次迭代。对于“高精确度”,请使用50-60次迭代。
快速反余弦实现,精确到约 0.5 度,可以基于 观察[0,1] 中的 x,acos(x) \xe2\x89\x88 \xe2\x88\x9a(2*(1-x ))。额外的比例因子可提高接近于零的精度。可以通过简单的二分搜索找到最佳因子。负参数按照 acos (-x) = \xcf\x80 - acos (x) 处理。
\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <string.h>\n#include <math.h>\n\n// Approximate acos(a) with relative error < 5.15e-3\n// This uses an idea from Robert Harley\'s posting in comp.arch.arithmetic on 1996/07/12\n// https://groups.google.com/forum/#!original/comp.arch.arithmetic/wqCPkCCXqWs/T9qCkHtGE2YJ\nfloat fast_acos (float a)\n{\n const float PI = 3.14159265f;\n const float C = 0.10501094f;\n float r, s, t, u;\n t = (a < 0) ? (-a) : a; // handle negative arguments\n u = 1.0f - t;\n s = sqrtf (u + u);\n r = C * u * s + s; // or fmaf (C * u, s, s) if FMA support in hardware\n if (a < 0) r = PI - r; // handle negative arguments\n return r;\n}\n\nfloat uint_as_float (uint32_t a)\n{\n float r;\n memcpy (&r, &a, sizeof(r));\n return r;\n}\n\nint main (void)\n{\n double maxrelerr = 0.0;\n uint32_t a = 0;\n do {\n float x = uint_as_float (a);\n float r = fast_acos (x);\n double xx = (double)x;\n double res = (double)r;\n double ref = acos (xx);\n double relerr = (res - ref) / ref;\n if (fabs (relerr) > maxrelerr) {\n maxrelerr = fabs (relerr);\n printf ("xx=% 15.8e res=% 15.8e ref=% 15.8e rel.err=% 15.8e\\n",\n xx, res, ref, relerr);\n }\n a++;\n } while (a);\n printf ("maximum relative error = %15.8e\\n", maxrelerr);\n return EXIT_SUCCESS;\n}\nRun Code Online (Sandbox Code Playgroud)\n\n上述测试脚手架的输出应类似于以下内容:
\n\nxx= 0.00000000e+000 res= 1.56272149e+000 ref= 1.57079633e+000 rel.err=-5.14060021e-003\nxx= 2.98023259e-008 res= 1.56272137e+000 ref= 1.57079630e+000 rel.err=-5.14065723e-003\nxx= 8.94069672e-008 res= 1.56272125e+000 ref= 1.57079624e+000 rel.err=-5.14069537e-003\nxx=-2.98023259e-008 res= 1.57887137e+000 ref= 1.57079636e+000 rel.err= 5.14071269e-003\nxx=-8.94069672e-008 res= 1.57887149e+000 ref= 1.57079642e+000 rel.err= 5.14075044e-003\nmaximum relative error = 5.14075044e-003\nRun Code Online (Sandbox Code Playgroud)\n