Создать таблицу поиска синуса в 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 в первый квадрант, используя простые триггерные тождества:
- грех (х) = - грех (-х), чтобы отобразить из квадранта IV в I
- sin (x) = sin (pi - x) для отображения из сектора II в I
Чтобы отобразить из квадранта III в I, примените обе тождества, то есть sin(x) = - sin (pi + x)
Поможет ли эта стратегия, зависит от того, насколько важно использование памяти в вашем случае. Но кажется бесполезным хранить в четыре раза больше значений, чем нужно, чтобы избежать сравнения и вычитания или двух во время поиска.
Во-вторых, я рекомендую Джереми измерить, лучше ли строить таблицу, чем просто использовать std::sin(). Даже с оригинальной большой таблицей вам придется тратить циклы во время каждого просмотра таблицы, чтобы преобразовать аргумент в ближайший шаг приращения pi/1000, и вы потеряете некоторую точность в процессе.
Если вы действительно пытаетесь обменять точность на скорость, вы можете попробовать аппроксимировать функцию sin (), используя только первые несколько членов расширения серии Тейлора.
- грех (х) = х - х ^3/3! + х ^5/5! ..., где ^ представляет вознесение в степень и! представляет факториал.
Конечно, для эффективности вы должны предварительно вычислить факториалы и использовать более низкие степени x для вычисления более высоких, например, использовать x^3 при вычислении x^5.
И последнее замечание - усеченный ряд Тейлора, приведенный выше, более точен для значений, близких к нулю, поэтому все еще стоит сопоставить первый или четвертый квадрант, прежде чем вычислять приблизительный синус.
Приложение: еще одно потенциальное улучшение, основанное на двух наблюдениях:
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);
}
Еще один момент: вызов тригонометрических функций дорогой. если вы хотите подготовить таблицу поиска для синуса с постоянным шагом - вы можете сэкономить время вычисления за счет некоторой потенциальной потери точности.
Считайте, что ваш минимальный шаг - "а". То есть вам нужен грех (а), грех (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, используя хитрость (pi/2 +/- angle) = +/- cos(angle). Так что сохраняйте 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);