Рассчитать косинус последовательности - PullRequest
8 голосов
/ 01 марта 2010

Я должен рассчитать следующее:

float2 y = CONSTANT;
for (int i = 0; i < totalN; i++)
   h[i] = cos(y*i);

totalN - большое число, поэтому я хотел бы сделать это более эффективным способом. Есть ли способ улучшить это? Я подозреваю, что это так, потому что, в конце концов, мы знаем, что является результатом cos (n), для n = 1..N, поэтому, возможно, есть некоторая теорема, которая позволяет мне вычислить это быстрее. Буду очень признателен за любую подсказку.

Заранее спасибо,

Federico

Ответы [ 9 ]

6 голосов
/ 01 марта 2010

Я не уверен, какие компромиссы между точностью и производительностью вы готовы сделать, но по этим ссылкам ведутся подробные обсуждения различных методов аппроксимации синусоид:

Веселье с синусоидами - http://www.audiomulch.com/~rossb/code/sinusoids/
Быстрый и точный синус / косинус - http://www.devmaster.net/forums/showthread.php?t=5784

Изменить (я думаю, что это ссылка "Дон Кросс", которая не работает на странице "Веселье с синусоидами"):

Оптимизация триггерных вычислений - http://groovit.disjunkt.com/analog/time-domain/fasttrig.html

6 голосов
/ 01 марта 2010

Используя одну из самых красивых формул математики, Формула Эйлера
exp(i*x) = cos(x) + i*sin(x)

, заменяющий x := n * phi:

cos(n*phi) = Re( exp(i*n*phi) )
sin(n*phi) = Im( exp(i*n*phi) )

exp(i*n*phi) = exp(i*phi) ^ n

Мощность ^n - это n повторных умножений. Поэтому вы можете вычислить cos(n*phi) и одновременно sin(n*phi) путем повторного комплексного умножения на exp(i*phi), начиная с (1+i*0).

Примеры кодов:

Python:

from math import *

DEG2RAD = pi/180.0 # conversion factor degrees --> radians
phi = 10*DEG2RAD # constant e.g. 10 degrees

c = cos(phi)+1j*sin(phi) # = exp(1j*phi)
h=1+0j
for i in range(1,10):
  h = h*c
  print "%d %8.3f"%(i,h.real)

или C:

#include <stdio.h>
#include <math.h>

// numer of values to calculate:
#define N 10

// conversion factor degrees --> radians:
#define DEG2RAD (3.14159265/180.0)

// e.g. constant is 10 degrees:
#define PHI (10*DEG2RAD)

typedef struct
{
  double re,im;
} complex_t;


int main(int argc, char **argv)
{
  complex_t c;
  complex_t h[N];
  int index;

  c.re=cos(PHI);
  c.im=sin(PHI);

  h[0].re=1.0;   
  h[0].im=0.0;
  for(index=1; index<N; index++)
  {
    // complex multiplication h[index] = h[index-1] * c;
    h[index].re=h[index-1].re*c.re - h[index-1].im*c.im; 
    h[index].im=h[index-1].re*c.im + h[index-1].im*c.re; 
    printf("%d: %8.3f\n",index,h[index].re);
  }
} 
4 голосов
/ 02 марта 2010

Может быть, самая простая формула

cos (n + y) = 2cos (n) cos (y) - cos (n-y).

Если вы предварительно вычислите константу 2 * cos (y), то каждое значение cos (n + y) может быть вычислено из предыдущих двух значений с одним умножением и одним вычитанием. В псевдокоде

h[0] = 1.0
h[1] = cos(y)
m = 2*h[1]
for (int i = 2; i < totalN; ++i)
  h[i] = m*h[i-1] - h[i-2]
3 голосов
/ 01 марта 2010

Вот метод, но он использует немного памяти для греха. Используются триггерные тождества:

cos(a + b) = cos(a)cos(b)-sin(a)sin(b)
sin(a + b) = sin(a)cos(b)+cos(a)sin(b)

Тогда вот код:

h[0] = 1.0;
double g1 = sin(y);
double glast = g1;
h[1] = cos(y);
for (int i = 2; i < totalN; i++){
    h[i] = h[i-1]*h[1]-glast*g1;
    glast = glast*h[1]+h[i-1]*g1;

}

Если я не допустил ошибок, то это должно быть сделано. Конечно, могут быть проблемы с округлением, так что имейте это в виду. Я реализовал это в Python, и это довольно точно.

1 голос
/ 07 марта 2010

Для решения проблем Кирка: все решения, основанные на повторяемости cos и sin, сводятся к вычислениям

x (k) = R x (k - 1),

где R - матрица, которая вращается на y, а x (0) - единичный вектор (1, 0). Если истинный результат для k - 1 равен x '(k - 1), а истинный результат для k - x' (k), то ошибка исходит из e (k - 1) = x (k - 1) - x ' (k - 1) - e (k) = R x (k - 1) - R x '(k - 1) = R e (k - 1) по линейности. Поскольку R - это то, что называется ортогональной матрицей , R e (k - 1) имеет ту же норму, что и e (k - 1), и ошибка растет очень медленно. (Причина, по которой он вообще растет, связана с округлением; компьютерное представление R в целом почти, но не совсем ортогонально, поэтому будет необходимо время от времени перезапускать повторение, используя операции триггера, в зависимости от точности требуется. Это все еще намного, намного быстрее, чем использование триггеров для вычисления каждого значения.)

1 голос
/ 07 марта 2010

Здесь есть несколько хороших ответов, но все они рекурсивные. Рекурсивный расчет не будет работать для функции косинуса при использовании арифметики с плавающей запятой; вы обязательно получите ошибки округления, которые быстро составят.

Рассмотрим расчет y = 45 градусов, всего N 10 000. В результате вы не получите 1 в качестве окончательного результата.

0 голосов
/ 01 марта 2010

Насколько точным должен быть полученный cos (x)? Если вы можете жить с некоторыми из них, вы можете создать справочную таблицу, сэмплируя единичную окружность с интервалами 2 * PI / N, а затем интерполировать между двумя соседними точками. N будет выбран для достижения желаемого уровня точности.

Что я не знаю, так это то, что интерполяция на самом деле дешевле, чем вычисление косинуса. Так как это обычно делается в микрокоде в современных процессорах, это может быть не так.

0 голосов
/ 01 марта 2010

Знание cos (n) не помогает - ваша математическая библиотека уже делает для вас такие тривиальные вещи.

Зная, что cos ((i + 1) y) = cos (i y + y) = cos (i y) cos (y) -sin (i y) sin (y) может помочь, если вы предварительно вычислите cos (y) и sin (y) и будете отслеживать как cos (i y), так и sin (i * y) по пути. Однако это может привести к некоторой потере точности - вам придется проверить.

0 голосов
/ 01 марта 2010

Вы можете сделать это, используя комплексные числа.

если вы определите x = sin (y) + i cos (y), cos (y * i) будет действительной частью x ^ i.

Вы можете вычислять все итеративно. Сложное умножение - 2 умножения плюс два сложения.

...