Создание таблицы поиска синуса в C++

Как я могу переписать следующий псевдокод на C++?

real array sine_table[-1000..1000]
    for x from -1000 to 1000
        sine_table[x] := sine(pi * x / 1000)

Мне нужно создать таблицу поиска sine_table.

6 ответов


вы можете уменьшить размер вашей таблицы до 25% от оригинала, сохраняя значения только для первого квадранта, т. е. для x в [0,pi/2].

для этого ваша процедура поиска просто должна сопоставить все значения x с первым квадрантом, используя простые тригонометрические идентификаторы:

  • sin (x) = - sin (- x), чтобы отобразить из квадранта IV в I
  • sin(x) = sin (pi - x), чтобы отобразить из квадранта II в I

чтобы отобразить из квадранта III в I, примените оба идентификатора, то есть sin (x) = - sin (pi + x)

будет ли эта стратегия зависит от того, насколько важно использование памяти в вашем случае. Но кажется расточительным хранить в четыре раза больше значений, чем вам нужно, чтобы избежать сравнения и вычитания или двух во время поиска.

Я поддерживаю рекомендацию Джереми измерить, лучше ли строить таблицу, чем просто использовать std::sin(). Даже с исходной большой таблицей вам придется тратить циклы во время каждого поиска таблицы, чтобы преобразовать аргумент до ближайшего приращения pi / 1000, и вы потеряете некоторую точность в этом процессе.

Если вы действительно пытаетесь обменять точность на скорость, вы можете попробовать аппроксимировать функцию sin (), используя только первые несколько членов расширения серии Тейлора.

  • sin (x) = x - x^3/3! + x^5/5! ..., где ^ представляет собой возвышение до власти и ! представляет факториал.

конечно, для эффективности, вы должны заранее вычислить факториалы и используйте более низкие степени x для вычисления более высоких, например, используйте x^3 при вычислении x^5.

одна заключительная точка, усеченный ряд Тейлора выше более точен для значений, близких к нулю, поэтому его все еще стоит сопоставить с первым или четвертым квадрантом перед вычислением приблизительного синуса.

добавление: Еще одно потенциальное улучшение, основанное на двух наблюдениях:--27--> 1. Вы можете вычислить любую тригонометрическую функцию, если вы можете вычислить как синус, так и косинус в первом Октант [0, pi / 4]
2. Расширение серии Тейлора с центром в нуле более точно около нуля

поэтому, если вы решите использовать усеченный ряд Тейлора, то вы можете улучшить точность (или использовать меньше терминов для аналогичной точности) путем сопоставления с синусом или Косинусом, чтобы получить угол в диапазоне [0, pi / 4], используя такие тождества, как sin(x) = cos(pi/2-x) и cos(x) = sin(pi/2-x) в дополнение к приведенным выше (например, если x > pi / 4 после сопоставления с первым квадрант.)

или если вы решите использовать поиск таблицы для синуса и Косинуса, вы можете обойтись двумя меньшими таблицами,которые охватывают только диапазон [0, pi/4] за счет другого возможного сравнения и вычитания при поиске для сопоставления с меньшим диапазоном. Тогда вы можете использовать меньше памяти для таблиц или использовать ту же память, но обеспечить более тонкую гранулярность и точность.


вы хотите с <cmath>.


long double sine_table[2001];
for (int index = 0; index < 2001; index++)
{
    sine_table[index] = std::sin(PI * (index - 1000) / 1000.0);
}

еще один момент: вызов тригонометрических функций является дорогостоящим. если вы хотите подготовить таблицу поиска для синуса с постоянным шагом - вы можете сэкономить время расчета за счет некоторой потенциальной потери точности.

рассмотрим ваш минимальный шаг - "a". То есть вам нужен грех(а), грех(2а), грех(3а)...

затем вы можете сделать следующий трюк: сначала вычислите sin(a) и cos (a). Затем для каждого последовательного шага используйте следующий тригонометрический равенства:

  • sin ([n+1] * a) = sin(n*a) * cos(a) + cos(n*a) * sin (a)
  • cos ([n+1] * a) = cos(n*a) * cos(a) - sin(n*a) * sin(a)

недостатком этого метода является то, что во время этой процедуры накапливается ошибка округления.



double table[1000] = {0};
for (int i = 1; i <= 1000; i++)
{
    sine_table[i-1] = std::sin(PI * i/ 1000.0);
}

double getSineValue(int multipleOfPi){ if(multipleOfPi == 0) return 0.0; int sign = 1; if(multipleOfPi < 0){ sign = -1;
} return signsine_table[signmultipleOfPi - 1]; }

вы можете уменьшить длину массива до 500, с помощью трюка sin(pi/2 +/- угол) = + / - cos (угол). Поэтому храните sin и cos от 0 до pi/4. Я не помню с головы, но это увеличило скорость моей программы.


еще одно приближение из книги или что-то

streamin ramp;
streamout sine;

float x,rect,k,i,j;

x = ramp -0.5;

rect = x * (1 - x < 0 & 2);
k = (rect + 0.42493299) *(rect -0.5) * (rect - 0.92493302) ;
i = 0.436501 + (rect * (rect + 1.05802));
j = 1.21551 + (rect * (rect - 2.0580201));
sine = i*j*k*60.252201*x;

полное обсуждение здесь: http://synthmaker.co.uk/forum/viewtopic.php?f=4&t=6457&st=0&sk=t&sd=a

Я предполагаю, что вы знаете, что использование деления намного медленнее, чем умножение на десятичное число, /5 всегда медленнее, чем *0.2

это просто приближение.

также:

streamin ramp;
streamin x;  // 1.5 = Saw   3.142 = Sin   4.5 = SawSin
streamout sine;
float saw,saw2;
saw = (ramp * 2 - 1) * x;
saw2 = saw * saw;

sine = -0.166667 + saw2 * (0.00833333 + saw2 * (-0.000198409 + saw2 * (2.7526e-006+saw2 * -2.39e-008)));
sine = saw * (1+ saw2 * sine);