Provavelmente, você conhece as seguintes proporções da escola:
Quando você se familiarizou com esta fórmula na infância, muito provavelmente, seu primeiro sentimento foi de dor devido ao fato de que essa fórmula deve ser lembrada. Isso é muito ruim, porque na verdade você não precisa se lembrar dessa fórmula - ela é exibida quando você gira o triângulo no papel. De fato, faço o mesmo quando escrevo esta fórmula. Essa interpretação será aparente no meio deste artigo. Mas agora, para deixar toda a diversão para mais tarde e adiar o momento em que você diz “Eureka!”, Vamos pensar sobre por que deveríamos pensar sobre essa fórmula.

Entrada
As funções trigonométricas sin() e cos() provavelmente as mais populares em computação gráfica, pois são a base para descrever qualquer forma redonda de maneira paramétrica. Entre os locais de sua possível aplicação estão a geração de círculos ou objetos volumétricos de revolução, ao calcular a transformada de Fourier, a geração procedural de ondas no plano aquático, geradores para um sintetizador de som de software e assim por diante. Em todos esses casos, sin() e cos() são chamados dentro do loop, como aqui:
 for(int n=0; n < num; n++) { const float t = 2.0f*PI*(float)n/(float)num; const float s = sinf(t); const float c = cosf(t);  
Começamos a reescrever o ciclo de forma incremental (veja o código abaixo), portanto é mais fácil imaginar que na iteração n deste ciclo com a fase t , a próxima iteração, n+1 , considerará sin() e cos() para t+f . Em outras palavras, sin(t) e cos(t) são contados para nós e precisamos contar sin(t+f) e cos(t+f) :
 const float f = 2.0f*PI/(float)num; const float t = 0.0f; for( int n=0; n < num; n++ ) { const float s = sinf(t); const float c = cosf(t);  
Não importa como obtivemos t qual é seu intervalo de valores (no exemplo acima - ) A única coisa que nos incomoda é que existe um loop que chama constantemente sin() e cos() com um parâmetro que aumenta em etapas constantes (neste caso, ) Este artigo é sobre como otimizar esse código para velocidade de forma que os mesmos cálculos possam ser executados sem o uso das funções sin() ou cos() (no loop interno) e até mesmo da função sincos() mais rápida.
Mas se você olhar para a primeira fórmula do artigo, veremos que, se e podemos reescrever isso como
 sin(t+f) = sin(f)*cos(t) + cos(f)*sin(t) cos(t+f) = cos(f)*cos(t) - sin(f)*sin(t) 
ou em outras palavras
 new_s = sin(f)*old_c + cos(f)*old_s new_c = cos(f)*old_c - sin(f)*old_s 
Como f é uma constante, sin(f) e cos(f) também. Chame-os de b respectivamente:
 new_s = b*old_c + a*old_s new_c = a*old_c - b*old_s 
Essa expressão pode ser usada diretamente em nosso código e, em seguida, obtemos um loop no qual funções trigonométricas caras (e de fato não) são chamadas!
 const float f = 2.0f*PI/(float)num; const float a = cosf(f); const float b = sinf(f); float s = 0.0f; float c = 1.0f; for( int n=0; n < num; n++ ) {  
Interpretação
Até agora, brincamos cegamente com matemática, sem entender o que realmente está acontecendo. Vamos reescrever o loop interno assim:
Alguns de vocês devem ter notado que essa é uma fórmula para rotacionar um objeto no espaço bidimensional. Se você ainda não entende isso, talvez o formulário da matriz o ajude.
\ left (\ begin {matrix} s_ {n + 1} \\ c_ {n + 1} \ end {matrix} \ right) = \ left (\ begin {matrix} a & b \\ -b & a \ end {matrix} \ right) \ cdot \ left (\ begin {matrix} s_ {n} \\ c_ {n} \ end {matrix} \ right)
De fato, sin(t) e cos(t) podem ser agrupados em um vetor de comprimento 1 e desenhados como um ponto na tela. Chame esse vetor x . Então x = \ {\ sin \ beta, \ cos \ beta \} . Portanto, a forma vetorial de expressão é onde R = \ left (\ begin {matrix} a & b \\ - b & a \ end {matrix} \ right) . Vemos que nosso loop executa uma pequena rotação bidimensional a cada iteração, de modo que x gira em círculo durante a execução do ciclo. Cada iteração x gira em radiano.
Então basicamente
esta é a fórmula do movimento pontual x = \ {\ cos \ alpha, \ sin \ alpha \} circunferência em incrementos de radiano. Para isso, construiremos um dos dois eixos de rotação, por exemplo, u = \ {\ cos \ beta, \ sin \ beta \} . O primeiro componente da rotação é a projeção. em . Desde e normalizada (tem um comprimento de 1), a projeção é o produto escalar. Portanto, e, é claro, o segundo componente é a anti-projeção, que pode ser encontrada projetando-se no eixo perpendicular, . Podemos criar esse vetor expandindo as coordenadas e mude o sinal para o oposto na primeira coordenada: 
Anotações
Normalmente, você deve poder executar essas pequenas rotações repetidas vezes. De fato | R | = \ left | \ begin {matriz} a & b \\ - b & a \ end {matriz} \ right | = a ^ 2 + b ^ 2 = \ sen ^ 2 \ alfa + \ cos ^ 2 \ alpha = 1 , o que significa que a matriz não aumenta nem diminui o espaço no qual é aplicado, o que significa que irá se mover em um círculo perfeito. No entanto, como os computadores não são precisos, irá se mover em espiral e eventualmente coincidir com o centro do círculo de rotação. Eu não tive esses problemas, mas acho que eles podem ocorrer com um num muito grande, ou seja, pequenos ângulos de viragem.
Exemplo
No Kindercrasher , a demo de 4096 bytes de 2008 (captura de tela no KDPV), um grupo de esferas pulsa na música. Para isso, contei a transformação de Fourier do som. Existem algoritmos que fazem isso em tempo real, por exemplo, FFT . No entanto, meu código deve caber em alguns kilobytes e eu decidi seguir o outro caminho. Em vez de implementar a FFT, escrevi a DFT por sua definição simples. Você pode conferir na wikipedia.
Minha função também utiliza um buffer de áudio estéreo de 16 bits, x , e retorna as primeiras 128 frequências do espectro sonoro do som y . Veja como o loop interno é organizado, o que executa 4096 vezes: nem uma única chamada para as funções sin() ou cos() , embora em outras implementações essas chamadas sejam.
 #include <math.h> void iqDFT12( float *y, const short *x ) { for( int i=0; i<128; i++ ) { const float wi = (float)i*(2.0f*3.1415927f/4096.0f); const float sii = sinf( wi ); const float coi = cosf( wi ); float co = 1.0f; float si = 0.0f; float acco = 0.0f; float acsi = 0.0f; for( int j=0; j<4096; j++ ) { const float f = (float)(x[2*j+0]+x[2*j+1]); const float oco = co; acco += co*f; co = co*coi - si*sii; acsi += si*f; si = si*coi + oco*sii; } y[i] = sqrtf(acco*acco+acsi*acsi)*(1.0f/32767.0f); } }