Существует ли точное приближение функции acos ()?
мне нужно acos()
функция с двойной точностью в вычислительном шейдере. Так как нет встроенной функции acos()
в GLSL с двойной точностью я попытался реализовать свой собственный.
сначала я реализовал ряд Тейлора, как уравнение из серия Wiki-Taylor С предварительно вычисленными значениями факультета. Но это кажется неточным около 1. Максимальная ошибка была около 0.08 с 40 итерациями.
я тоже реализовал этот метод который очень хорошо работает на CPU с максимальной ошибкой -2.22045 e-16, но у меня есть некоторые проблемы с реализацией этого в шейдере.
В настоящее время я использую acos()
аппроксимирующая функция из здесь где кто-то разместил свои аппроксимационные функции на этой сайт. Я использую самую точную функцию этого сайта, и теперь я получаю максимальную ошибку -7.60454 e-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()
очень точное и, если возможно легко реализовать в шейдере?
некоторая системная информация:
- Nvidia GT 555M
- запуск OpenGL 4.3 с optirun
2 ответов
моя текущая точная реализация шейдера "acos ()" представляет собой смесь из обычной серии Тейлора и ответа от Бенс. С 40 итерациями я получаю точность 4.44089 e-16 до реализации "acos ()" из math.ч. Может быть, это не самое лучшее, но это работает для меня:
и вот это:
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 ()" вызывается только один раз, поэтому в этом нет необходимости.
NVIDIA GT 555M GPU-это устройство с вычислительной способностью 2.1, поэтому существует собственная аппаратная поддержка основных операций двойной точности, включая слитый multipy-add (FMA). Как и во всех графических процессорах NVIDIA, эмулируется операция квадратного корня. Я знаком с CUDA, но не с GLSL. Согласно версии 4.3 спецификация GLSL, он подвергает двойной точности FMA как функция fma()
и обеспечивает квадратный корень двойной точности,sqrt()
. Непонятно ли sqrt()
реализация правильно округляется в соответствии с IEEE-754 правила. Я предполагаю, что это так, по аналогии с CUDA.
вместо использования ряда Тейлора, хотелось бы использовать полином минимаксные аппроксимации, тем самым уменьшая количество требуемых терминов. Минимаксные аппроксимации обычно генерируются с использованием варианта алгоритм Ремеза. Для оптимизации скорости и точности необходимо использовать FMA. Оценка многочлена с помощью схема Горнера является снисходительным к высокому начислению. В приведенном ниже коде используется схема Хорнера второго порядка. Как в DanceIgel это ответ, acos
удобно вычисляется с помощью asin
аппроксимация как основной строительный блок в сочетании со стандартными математическими тождествами.
С векторами теста 400М, максимальная относительная ошибка увиденная с кодом ниже была 2,67 э-16, пока максимум ulp наблюдаемая ошибка-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;
}