Создать таблицу поиска синуса в C ++ - PullRequest
14 голосов
/ 11 сентября 2010

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

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

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

Ответы [ 6 ]

30 голосов
/ 11 сентября 2010

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

5 голосов
/ 11 сентября 2010

Вам понадобится функция std::sin() от <cmath>.

4 голосов
/ 19 сентября 2010

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

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

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

  • 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)

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

4 голосов
/ 11 сентября 2010
long double sine_table[2001];
for (int index = 0; index < 2001; index++)
{
    sine_table[index] = std::sin(PI * (index - 1000) / 1000.0);
}
3 голосов
/ 30 марта 2013

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

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);
3 голосов
/ 11 сентября 2010

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

<p>double getSineValue(int multipleOfPi){
     if(multipleOfPi == 0) return 0.0;
     int sign = 1;
     if(multipleOfPi < 0){
         sign = -1;<br>
     }
     return sign<em>sine_table[sign</em>multipleOfPi - 1];
}

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

...