Вычисление синуса и Косинуса в одном кадре

у меня есть научный код, который использует как синус, так и косинус одного и того же аргумента (мне в основном нужна сложная экспонента этого аргумента). Мне было интересно, можно ли сделать это быстрее, чем вызывать синусоидальные и косинусные функции отдельно.

также мне нужна только точность 0.1%. Итак, есть ли способ найти триггерные функции по умолчанию и усечь серию мощности для скорости?

одна вещь, которую я имею в виду, есть ли способ, чтобы выполнить операция остатка такая, что результат всегда положительный? В моем собственном алгоритме я использовал x=fmod(x,2*pi); но тогда мне нужно будет добавить 2pi, если x отрицательный (меньший домен означает, что я могу использовать более короткий ряд мощности)

EDIT: LUT оказался лучшим подходом для этого, однако я рад, что узнал о других методах приближения. Я также посоветую использовать явное приближение средней точки. Вот что я в итоге сделал:--5-->

const int N = 10000;//about 3e-4 error for 1000//3e-5 for 10 000//3e-6 for 100 000
double *cs = new double[N];
double *sn = new double[N];
for(int i  =0;i<N;i++){
    double A= (i+0.5)*2*pi/N;
    cs[i]=cos(A);
    sn[i]=sin(A);
}

следующая часть приближается (медианы) синусно-косинусный(2*Пи*(wс2+Т[Дж]*(cotp*Т[Дж]-туалет)))

double A=(wc2+t[j]*(cotp*t[j]-wc));
int B =(int)N*(A-floor(A));
re += cs[B]*f[j];
im += sn[B]*f[j];

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

double fastsin(double x){
    x=x-floor(x/2/pi)*2*pi-pi;//this line can be improved, both inside this 
                              //function and before you input it into the function

    double x2 = x*x;
    return (((0.00015025063885163012*x2- 
   0.008034350857376128)*x2+ 0.1659789684145034)*x2-0.9995812174943602)*x;} //7th order chebyshev approx

5 ответов


Если вы ищете быструю оценку с хорошей (но не высокой) точностью с powerseries, вы должны использовать разложение в полиномах Чебышева: табулируйте коэффициенты (вам понадобится очень мало для точности 0,1%) и оцените разложение с рекурсионными отношениями для этих полиномов (это действительно очень просто).

ссылки:

  1. Табулированных коэффициентов: http://www.ams.org/mcom/1980-34-149/S0025-5718-1980-0551302-5/S0025-5718-1980-0551302-5.pdf
  2. оценка расширения Чебышева:https://en.wikipedia.org/wiki/Chebyshev_polynomials

вам нужно (a) получить аргумент "reduced" в диапазоне-pi/2..+pi / 2 и, следовательно, затем (b) обрабатывать знак в ваших результатах, когда аргумент фактически должен был быть в "другой" половине полного элементарного интервала-pi..+пи. Эти аспекты не должны создавать серьезной проблемы:

  1. определите (и" запомните " как целое число 1 или -1) знак в исходном угле и продолжите с абсолютным значением.
  2. используйте функцию по модулю, чтобы уменьшить до интервала 0..2Пи
  3. определите (и" запомните "как целое число 1 или -1), находится ли оно во" второй " половине, и если да, вычитайте pi*3/2, иначе вычитайте pi/2. Примечание: это эффективно взаимозаменяет синус и косинус (кроме знаков); примите это в учет в итоговой оценке.

Это завершает шаг, чтобы получить угол в-pi / 2..+pi / 2 После оценки синуса и Косинуса с помощью разложений Хеба примените "флаги" шагов 1 и 3 выше, чтобы получить правильные знаки в значениях.


просто создайте таблицу поиска. Следующие позволит вам найти Sin и cos любого значения Радиан от-2Пи и 2Пи.

// LOOK UP TABLE
var LUT_SIN_COS = [];
var N = 14400;
var HALF_N = N >> 1;
var STEP = 4 * Math.PI / N;
var INV_STEP = 1 / STEP;
// BUILD LUT
for(var i=0, r = -2*Math.PI; i < N; i++, r += STEP) {
    LUT_SIN_COS[2*i] = Math.sin(r);
    LUT_SIN_COS[2*i + 1] = Math.cos(r);
}

вы индексируете в таблицу поиска по:

var index = ((r * INV_STEP) + HALF_N) << 1;
var sin = LUT_SIN_COS[index];
var cos = LUT_SIN_COS[index + 1];

вот скрипка, которая отображает ошибку%, которую вы можете ожидать от разных размеров LUTS http://jsfiddle.net/77h6tvhj/

редактировать вот ideone (c++) с ~benchmark~ против float sin и cos. http://ideone.com/SGrFVG для любого ориентира на ideone.com стоит лут в 5 раз быстрее.


один из способов пойти было бы узнать, как реализовать CORDIC. Это не сложно и довольно интересно интеллектуально. Это дает вам как Косинус и синус. Википедия дает пример MATLAB это должно быть легко адаптироваться на C++.

заметьте что вы можете увеличить скорость и уменьшить точность просто путем понижать параметр n.


о вашем втором вопросе, он уже был задан здесь (in C). Кажется, что простого пути нет.


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

В приведенном ниже примере предполагается, что угол в диапазоне от 0 до 2π:

 double c = cos(angle);
 double s = sqrt(1.0-c*c);
 if(angle>pi)s=-s;

для поплавков с одной точностью Microsoft использует 11-градусное полиномиальное приближение для синуса, 10-градусное для Косинуса:XMScalarSinCos. Они также имеют более быструю версию, XMScalarSinCosEst,которая использует полиномы более низкой степени.

Если вы не в Windows, вы найдете тот же код + коэффициенты ВКЛ geometrictools.com под лицензией Boost.