В настоящее время я работаю над приближением косинуса. Поскольку конечным целевым устройством является самостоятельная разработка, работающая с 32-битным ALU/LU с плавающей запятой, и существует специализированный компилятор для C, я не могу использовать математические функции библиотеки c (cosf,...). Я стремлюсь кодировать различные методы, которые отличаются точностью и количеством инструкций/циклов.
Я уже пробовал много разных алгоритмов аппроксимации, начиная с fdlibm, расширения Тейлора, аппроксимации Паде, алгоритма Ремеза с использованием клена и так далее....
Но как только я реализую их, используя только точность с плавающей запятой, происходит значительная потеря точности. И будьте уверены: я знаю, что с двойной точностью гораздо более высокая точность вообще не проблема...
Прямо сейчас у меня есть некоторые приближения, которые точны до нескольких тысяч ulp около pi/2 (диапазон, в котором возникают самые большие ошибки), и я чувствую, что я ограничен преобразованиями с одинарной точностью.
Чтобы решить проблему сокращения аргумента темы: ввод в радианах. я предполагаю, что уменьшение аргумента приведет к еще большей потере точности из-за деления/умножения.... поскольку мой общий диапазон ввода составляет всего 0..pi, я решил уменьшить аргумент до 0..pi/2.
Поэтому мой вопрос: кто-нибудь знает одноточное приближение к функции косинуса с высокой точностью (и в лучшем случае с высокой эффективностью)? Существуют ли какие-либо алгоритмы, оптимизирующие приближения для одинарной точности? Знаете ли вы, вычисляет ли встроенная функция cosf значения с одинарной или двойной точностью? ~
float ua_cos_v2(float x)
{
float output;
float myPi = 3.1415927410125732421875f;
if (x < 0) x = -x;
int quad = (int32_t)(x*0.63661977236f);//quad = x/(pi/2) = x*2/pi
if (x<1.58f && x> 1.57f) //exclude approximation around pi/2
{
output = -(x - 1.57079637050628662109375f) - 2.0e-12f*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f) + 0.16666667163372039794921875f*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f) + 2.0e-13f*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)+ 0.000198412701138295233249664306640625f*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f)*(x - 1.57079637050628662109375f);
output -= 4.37E-08f;
}
else {
float param_x;
int param_quad = -1;
switch (quad)
{
case 0:
param_x = x;
break;
case 1:
param_x = myPi - x;
param_quad = 1;
break;
case 2:
param_x = x - myPi;
break;
case 3:
param_x = 2 * myPi - x;
break;
}
float c1 = 1.0f,
c2 = -0.5f,
c3 = 0.0416666679084300994873046875f,
c4 = -0.001388888922519981861114501953125f,
c5 = 0.00002480158218531869351863861083984375f,
c6 = -2.75569362884198199026286602020263671875E-7f,
c7 = 2.08583283978214240050874650478363037109375E-9f,
c8 = -1.10807162057025010426514199934899806976318359375E-11f;
float _x2 = param_x * param_x;
output = c1 + _x2*(c2 + _x2*(c3 + _x2*(c4 + _x2*(c5 + _x2*(c6 + _x2*(c7
+ _x2* c8))))));
if (param_quad == 1 || param_quad == 0)
output = -output;
}
return output;
}
~
если я забыл какую-либо информацию, пожалуйста, не стесняйтесь спрашивать!
заранее спасибо
cos(x*pi)
для0<=x<=1
? В любом случае, прежде чем применять какую-либо полиномиальную аппроксимацию, вы должны уменьшить входной диапазон до[-pi/4, pi/4]
и использовать такие тождества, какcos(x+pi/2) = -sin(x)
. - person chtz   schedule 16.09.2020float
, ошибка будет огромной. - person Eric Postpischil   schedule 16.09.2020float
. Второй, p1, равен π/2−p0 (предварительно рассчитанный, результат записан в исходный код). Тогда π/2−x можно точно вычислить с точностьюfloat
с точностьюp0-x+p1
. Когда x являетсяfloat
ближайшим π/2, это дает ошибку около ⅓ ULP. Кроме того, нам нужно будет увидеть код, который вы используете. - person Eric Postpischil   schedule 16.09.2020int b = 0x3fc90fdb; //pi/2 float p0 = *((float*)&b); float p1 = -4.371139000186242830836e-8f; float cos = p0 - x + p1;
, по крайней мере, для очень небольших интервалов, таких как 1.5707f .. 1.5709f. Есть ли у вас какие-либо другие идеи для приближений, которые можно использовать для остальной части интервала, скажем, от 1,4 до 1,9? - person Dexter S   schedule 16.09.2020helper_sin(sub_range_x)
иhelper_cos()
для решенияmy_cos(wider_x_range)
? - person chux - Reinstate Monica   schedule 16.09.2020cosd(some_degree_x)
, а неcos()
, так как уменьшение аргумента легче выполнить быстро и точно, чем начинать с радианов. - person chux - Reinstate Monica   schedule 16.09.2020