Создание таблицы поиска синуса в 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] за счет другого возможного сравнения и вычитания при поиске для сопоставления с меньшим диапазоном. Тогда вы можете использовать меньше памяти для таблиц или использовать ту же память, но обеспечить более тонкую гранулярность и точность.
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);