我需要在计算着色器中具有双精度的acos()
函数。由于GLSL中没有acos()
的内置函数具有双精度,我试图实现自己的函数。
首先,我实施了一个泰勒系列,如Wiki - Taylor series中的等式,具有预先计算的教师价值。但这似乎在1左右不准确。最大误差约为0.08,有40次迭代。
我还实现了this method,它在CPU上工作得很好,最大错误为-2.22045e-16,但我在着色器中实现这个有一些麻烦。
目前,我正在使用here的acos()
近似函数,其中有人在this网站上发布了他的近似函数。我正在使用本网站最准确的功能,现在我的最大误差为-7.60454e-08,但错误也太高。
我的这个功能的代码是:
double myACOS(double x)
{
double part[4];
part[0] = 32768.0/2835.0*sqrt(2.0-sqrt(2.0+sqrt(2.0+sqrt(2.0+2.0*x))));
part[1] = 256.0/135.0*sqrt(2.0-sqrt(2.0+sqrt(2.0+2.0*x)));
part[2] = 8.0/135.0*sqrt(2.0-sqrt(2.0+2.0*x));
part[3] = 1.0/2835.0*sqrt(2.0-2.0*x);
return (part[0]-part[1]+part[2]-part[3]);
}
有人知道acos()
的另一种实现方法是非常准确的 - 如果可能的话 - 很容易在着色器中实现?
一些系统信息:
答案 0 :(得分:2)
我目前对'acos()'的准确着色器实现是通常的Taylor系列和Bence的答案。通过40次迭代,我得到了math.h中'acos()'实现的精度4.44089e-16。也许它不是最好的,但它对我有用:
这就是:
double myASIN2(double x)
{
double sum, tempExp;
tempExp = x;
double factor = 1.0;
double divisor = 1.0;
sum = x;
for(int i = 0; i < 40; i++)
{
tempExp *= x*x;
divisor += 2.0;
factor *= (2.0*double(i) + 1.0)/((double(i)+1.0)*2.0);
sum += factor*tempExp/divisor;
}
return sum;
}
double myASIN(double x)
{
if(abs(x) <= 0.71)
return myASIN2(x);
else if( x > 0)
return (PI/2.0-myASIN2(sqrt(1.0-(x*x))));
else //x < 0 or x is NaN
return (myASIN2(sqrt(1.0-(x*x)))-PI/2.0);
}
double myACOS(double x)
{
return (PI/2.0 - myASIN(x));
}
任何评论,可以做得更好吗?例如,使用LUT作为因子的值,但在我的着色器'acos()'中只调用一次,因此不需要它。
答案 1 :(得分:2)
NVIDIA GT 555M GPU是一款具有2.1计算能力的设备,因此基本的双精度操作支持本机硬件,包括fused multipy-add(FMA)。与所有NVIDIA GPU一样,仿真平方根操作。我熟悉CUDA,但不熟悉GLSL。根据{{3}}的4.3版,它将双精度FMA公开为函数fma()
,并提供双精度平方根sqrt()
。目前尚不清楚sqrt()
实施是否根据GLSL specification规则正确舍入。我将假设它与CUDA类似。
不使用泰勒级数,而是希望使用多项式IEEE-754,从而减少所需的项数。 Minimax近似通常使用minimax approximation的变体生成。为了优化速度和准确性,FMA的使用至关重要。使用Remez algorithm对多项式的求值是高度累积的。在下面的代码中,使用二阶Horner方案。与在DanceIgel Horner scheme中一样,acos
可以使用asin
近似值作为基本构建块以及标准数学身份来方便地计算。
使用400M测试向量时,下面的代码看到的最大相对误差为2.67e-16,而观察到的最大answer误差为1.442 ulp。
/* compute arcsin (a) for a in [-9/16, 9/16] */
double asin_core (double a)
{
double q, r, s, t;
s = a * a;
q = s * s;
r = 5.5579749017470502e-2;
t = -6.2027913464120114e-2;
r = fma (r, q, 5.4224464349245036e-2);
t = fma (t, q, -1.1326992890324464e-2);
r = fma (r, q, 1.5268872539397656e-2);
t = fma (t, q, 1.0493798473372081e-2);
r = fma (r, q, 1.4106045900607047e-2);
t = fma (t, q, 1.7339776384962050e-2);
r = fma (r, q, 2.2372961589651054e-2);
t = fma (t, q, 3.0381912707941005e-2);
r = fma (r, q, 4.4642857881094775e-2);
t = fma (t, q, 7.4999999991367292e-2);
r = fma (r, s, t);
r = fma (r, s, 1.6666666666670193e-1);
t = a * s;
r = fma (r, t, a);
return r;
}
/* Compute arccosine (a), maximum error observed: 1.4316 ulp
Double-precision factorization of π courtesy of Tor Myklebust
*/
double my_acos (double a)
{
double r;
r = (a > 0.0) ? -a : a; // avoid modifying the "sign" of NaNs
if (r > -0.5625) {
/* arccos(x) = pi/2 - arcsin(x) */
r = fma (9.3282184640716537e-1, 1.6839188885261840e+0, asin_core (r));
} else {
/* arccos(x) = 2 * arcsin (sqrt ((1-x) / 2)) */
r = 2.0 * asin_core (sqrt (fma (0.5, r, 0.5)));
}
if (!(a > 0.0) && (a >= -1.0)) { // avoid modifying the "sign" of NaNs
/* arccos (-x) = pi - arccos(x) */
r = fma (1.8656436928143307e+0, 1.6839188885261840e+0, -r);
}
return r;
}