Что на самом деле делает эта 16-битная функция Cosine?

Я хотел бы думать, что я достаточно порядочен в рассмотрении кода других людей … но я в растерянности. Это происходит из библиотеки Doom3 Math. Я полагаю, что это существовало в GPL начиная с Quake 1. Обратите внимание, что это действительно ссылка на math.h. Я предполагаю, что есть способ вычислить косинус … но я не могу понять это. Кто-нибудь объяснит?

    ID_INLINE float idMath::Cos16( float a ) {
float s, d;

if ( ( a < 0.0f ) || ( a >= TWO_PI ) ) {
a -= floorf( a / TWO_PI ) * TWO_PI;
}
#if 1
if ( a < PI ) {
if ( a > HALF_PI ) {
a = PI - a;
d = -1.0f;
} else {
d = 1.0f;
}
} else {
if ( a > PI + HALF_PI ) {
a = a - TWO_PI;
d = 1.0f;
} else {
a = PI - a;
d = -1.0f;
}
}
#else
a = PI - a;
if ( fabs( a ) >= HALF_PI ) {
a = ( ( a < 0.0f ) ? -PI : PI ) - a;
d = 1.0f;
} else {
d = -1.0f;
}
#endif
s = a * a;
return d * ( ( ( ( ( -2.605e-07f * s + 2.47609e-05f ) * s - 1.3888397e-03f ) * s + 4.16666418e-02f ) * s - 4.999999963e-01f ) * s + 1.0f );
}

3

Решение

Все условные вещи выглядят так, как будто они складывают угол вниз до одного квадранта (или, может быть, октанта, я не могу понять это …;)) и записываем поправочный коэффициент (d).

Последняя строка выполняет некое полиномиальное приближение 10-го порядка (возможно, Тейлор или же Чебышева?).* Но это работает только на четных полномочиях, так как cos это четная функция. Это также использует Метод Хорнера избегать прямого вычисления больших мощностей несколько раз.

Затем он повторно применяет правильный знак, используя d,


* Чтобы убедиться, что это «довольно» точно, попробуйте запустить следующий код в Octave (вы можете сделать это онлайн, например, Вот):

% One quadrant
a = (-pi/2):0.1:(+pi/2);

% Exact result
y_exact = cos(a);

% Our approximation
s = a .* a;
y_approx = (((((-2.605e-07 .* s + 2.47609e-05) .* s - 1.3888397e-03) .* s + 4.16666418e-02) .* s - 4.999999963e-01) .* s + 1);

% Plot
hold on
plot(a, y_exact,  'b')
plot(a, y_approx, 'r')

5

Другие решения

Других решений пока нет …