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

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

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

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


person user466444    schedule 10.09.2010    source источник
comment
В какой части задания вам нужна помощь? Вызов функции sin? Справляетесь с тем, что вам нужны отрицательные индексы? Объявить массив? Написание цикла for на С++? Узнать имя типа в С++, который представляет собой действительное число? Все они?   -  person Steve Jessop    schedule 11.09.2010
comment
Возможно, вы захотите проверить производительность вашей таблицы поиска, когда она будет готова, и убедиться, что она на самом деле быстрее, чем просто вызов sin() (или sinf()). Современные ЦП работают намного быстрее, чем современная ОЗУ, поэтому вы можете обнаружить, что поиск результата в таблице занимает больше времени, чем просто повторное вычисление результата (поскольку вычисление результата может быть выполнено полностью на ЦП).   -  person Jeremy Friesner    schedule 11.09.2010
comment
Мало того, у вас также будет больше кода, и вы будете выбрасывать некоторые строки из кеша ЦП. Вам нужно протестировать производительность в реальной рутине, а не только на тестовом стенде.   -  person Loren Pechtel    schedule 11.09.2010
comment
Это может быть для встроенной системы или FPGA, где поиск будет быстрее, чем вычисление x — (x^3)/(3!) + (x^5)/(5!) — (x^7)/(7!) + ...   -  person Mike DeSimone    schedule 11.09.2010
comment
Выбранный ответ дает решение вопроса Как вычислить значения функции синуса без использования встроенного в язык метода sin. В этом случае Тейлор является наиболее известным алгоритмом для программной реализации, но компьютеры АЛУ, калькуляторы и другие цифровые системы часто эмулируют функцию синуса (и многие другие) аппаратно, реализуя CORDIC (примеры ). Он занимает меньше места, чем полная таблица поиска.   -  person mins    schedule 29.10.2020


Ответы (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(), используя только первые несколько членов разложения в ряд Тейлора.

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

person Alex Blakemore    schedule 10.09.2010
comment
Кроме того, таблицу четвертных синусов можно использовать для нахождения косинусов с помощью аналогичного метода переназначения квадрантов. - person Mike DeSimone; 11.09.2010
comment
В первом идентификаторе есть опечатка, должно быть sin (-x) = -sin(x) - person Ben Voigt; 19.09.2010
comment
Спасибо, что поймали этого Бена, я соответственно исправил свой ответ. - person Alex Blakemore; 20.09.2010
comment
Вы можете уменьшить размер таблицы до 25 % от исходного, сохранив значения только для первого квадранта. И в любом случае, чтобы получить фактические значения для [-pi, +pi], порядок ряда должен быть не менее 10, порядок 6 обеспечивает хорошее приближение для [-pi/2, +pi/2] (графики) - person mins; 30.10.2020

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

Считайте, что ваш минимальный шаг — «а». То есть вам нужен sin(a), sin(2a), sin(3a),...

Тогда вы можете проделать следующий трюк: сначала вычислить 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)

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

person valdo    schedule 19.09.2010

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

person Mike DeSimone    schedule 10.09.2010


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 +/- angle) = +/- cos(angle). Поэтому сохраните sin и cos от 0 до pi/4. Я не помню из головы, но это увеличило скорость моей программы.

person Master Yoda    schedule 10.09.2010
comment
Одно предостережение. Имя аргумента для getSineValue() может вводить в заблуждение. Он действительно представляет собой число, кратное Пи/1000, поэтому его можно назвать тысячными долей Пи. Не пытаюсь быть педантичным, но кого-то может легко сбить с толку тот факт, что getSineValue(500) означает sin(pi/2) - person Alex Blakemore; 11.09.2010
comment
Меня забавляет оптимизация запуска цикла for с единицы вместо нуля. :) Обратите внимание, что в цикле есть ошибка, он записывает в таблицу [1000], которая не является допустимым индексом в массиве (это единица после конца) - person Jeremy Friesner; 11.09.2010
comment
Прошу прощения за ошибку цикла. На самом деле я имел в виду хранение от 1 до 1000, поскольку 0 - это не очевидный случай. Я отредактировал код, чтобы исправить это. - person Master Yoda; 12.09.2010
comment
Я думаю, что это все же не совсем правильно... например, теперь sine_table[0] будет иметь значение sin(PI*1/1000.0), тогда как правильное значение sin(0) должно быть равно нулю. - person Jeremy Friesner; 14.09.2010
comment
вот почему это здесь: return signsine_table[signmultipleOfPi - 1]; - person Master Yoda; 14.09.2010

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

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);
person DeltaEnfieldWaid    schedule 29.03.2013

person    schedule
comment
1) Не забывайте #include <cmath> 2) Это фактически создает на один элемент меньше; это эквивалентно sine_table[-1000..999] в псевдокоде. - person Mike DeSimone; 11.09.2010