Você pode usar logaritmos para se livrar da divisão. Para (x,y) no primeiro quadrante:
z=log2(y)−log2(x)atan2(y,x)=atan(y/x)=atan(2z)
Figura 1. Gráfico de atan(2z)
Você precisaria aproximar atan(2z) no intervalo −30<z<30 para obter a precisão necessária de 1E-9. Você pode tirar proveito da simetria atan(2−z)=π2−atan(2z)ou, alternativamente, garantir que(x,y)esteja em um octante conhecido. Para aproximar olog2(a) :
b=floor(log2(a))c=a2blog2(a)=b+log2(c)
b pode ser calculado encontrando a localização do bit diferente de zero mais significativo. c pode ser calculado com uma mudança de bit. Você precisaria aproximar olog2(c) no intervalo1≤c<2 .
Figura 2. Gráfico do log2(c)
Para seus requisitos de precisão, interpolação linear e amostragem uniforme, 214+1=16385 amostras do log2(c) e 30×212+1=122881 amostras de atan(2z) para 0<z<30 devem ser suficientes. A última tabela é bem grande. Com isso, o erro devido à interpolação depende muito de z :
Figura 3. atan(2z) aproxima o maior erro absoluto para diferentes faixas de z (eixo horizontal) para diferentes números de amostras (32 a 8192) por unidade de intervalo de z . O maior erro absoluto para 0≤z<1 (omitido) é ligeiramente menor que para o floor(log2(z))=0 .
A tabela atan(2z) pode ser dividida em várias subtabelas que correspondem a 0≤z<1 e piso diferente ( log 2 ( z ) )floor(log2(z)) com z≥1 , o que é fácil de calcular. Os comprimentos da tabela podem ser escolhidos como guiado pela Fig. 3. O índice dentro da subtabela pode ser calculado por uma simples manipulação de cadeia de bits. Para seus requisitos de precisão, as subtabelas atan(2z) terão um total de 29217 amostras se você estender o intervalo de z para 0≤z<32 por simplicidade.
Para referência posterior, aqui está o script Python desajeitado que eu usei para calcular os erros de aproximação:
from numpy import *
from math import *
N = 10
M = 20
x = array(range(N + 1))/double(N) + 1
y = empty(N + 1, double)
for i in range(N + 1):
y[i] = log(x[i], 2)
maxErr = 0
for i in range(N):
for j in range(M):
a = y[i] + (y[i + 1] - y[i])*j/M
if N*M < 1000:
print str((i*M + j)/double(N*M) + 1) + ' ' + str(a)
b = log((i*M + j)/double(N*M) + 1, 2)
err = abs(a - b)
if err > maxErr:
maxErr = err
print maxErr
y2 = empty(N + 1, double)
for i in range(1, N):
y2[i] = -1.0/16.0*y[i-1] + 9.0/8.0*y[i] - 1.0/16.0*y[i+1]
y2[0] = -1.0/16.0*log(-1.0/N + 1, 2) + 9.0/8.0*y[0] - 1.0/16.0*y[1]
y2[N] = -1.0/16.0*y[N-1] + 9.0/8.0*y[N] - 1.0/16.0*log((N+1.0)/N + 1, 2)
maxErr = 0
for i in range(N):
for j in range(M):
a = y2[i] + (y2[i + 1] - y2[i])*j/M
b = log((i*M + j)/double(N*M) + 1, 2)
if N*M < 1000:
print a
err = abs(a - b)
if err > maxErr:
maxErr = err
print maxErr
y2[0] = 15.0/16.0*y[0] + 1.0/8.0*y[1] - 1.0/16.0*y[2]
y2[N] = -1.0/16.0*y[N - 2] + 1.0/8.0*y[N - 1] + 15.0/16.0*y[N]
maxErr = 0
for i in range(N):
for j in range(M):
a = y2[i] + (y2[i + 1] - y2[i])*j/M
b = log((i*M + j)/double(N*M) + 1, 2)
if N*M < 1000:
print str(a) + ' ' + str(b)
err = abs(a - b)
if err > maxErr:
maxErr = err
print maxErr
P = 32
NN = 13
M = 8
for k in range(NN):
N = 2**k
x = array(range(N*P + 1))/double(N)
y = empty((N*P + 1, NN), double)
maxErr = zeros(P)
for i in range(N*P + 1):
y[i] = atan(2**x[i])
for i in range(N*P):
for j in range(M):
a = y[i] + (y[i + 1] - y[i])*j/M
b = atan(2**((i*M + j)/double(N*M)))
err = abs(a - b)
if (i*M + j > 0 and err > maxErr[int(i/N)]):
maxErr[int(i/N)] = err
print N
for i in range(P):
print str(i) + " " + str(maxErr[i])
O erro máximo local de aproximação de uma função f(x) por interpolação linear f ( x ) a partir de amostras de f ( x ) , feita por amostragem uniforme com intervalo de amostragem Δ x , pode ser aproximada analiticamente por:f^(x)f(x)Δx
fˆ(x)−f(x)≈(Δx)2limΔx→0f(x)+f(x+Δx)2−f(x+Δx2)(Δx)2=(Δx)2f′′(x)8,
onde f′′(x) é a segunda derivada de f(x) e x está no máximo local do erro absoluto. Com o exposto, obtemos as aproximações:
atanˆ(2z)−atan(2z)≈(Δz)22z(1−4z)ln(2)28(4z+1)2,log2ˆ(a)−log2(a)≈−(Δa)28a2ln(2).
Como as funções são côncavas e as amostras correspondem à função, o erro ocorre sempre em uma direção. O erro absoluto máximo local pode ser reduzido pela metade se o sinal do erro for alternado para frente e para trás uma vez a cada intervalo de amostragem. Com interpolação linear, é possível obter resultados próximos ao ideal pré-filtrando cada tabela com:
y[k]=⎧⎩⎨⎪⎪b2x[k−2]c1x[k−1]+b1x[k−1]b0x[k]+c0x[k]+b0x[k]+b1x[k+1]+c1x[k+1]+b2x[k+2]if k=0,if 0<k<N,if k=N,
where x and y are the original and the filtered table both spanning 0≤k≤N and the weights are c0=98,c1=−116,b0=1516,b1=18,b2=−116. The end conditioning (first and last row in the above equation) reduces error at the ends of the table compared to using samples of the function outside of the table, because the first and the last sample need not be adjusted to reduce the error from interpolation between it and a sample just outside the table. Subtables with different sampling intervals should be prefiltered separately. The values of the weights c0,c1 were found by minimizing sequentially for increasing exponent N the maximum absolute value of the approximate error:
(Δx)NlimΔx→0(c1f(x−Δx)+c0f(x)+c1f(x+Δx))(1−a)+(c1f(x)+c0f(x+Δx)+c1f(x+2Δx))a−f(x+aΔx)(Δx)N=⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪(c0+2c1−1)f(x)01+a−a2−c02(Δx)2f′′(x)if N=0,∣∣∣c1=1−c02if N=1,if N=2,∣∣∣c0=98
for inter-sample interpolation positions 0≤a<1, with a concave or convex function f(x) (for example f(x)=ex). With those weights solved, the values of the end conditioning weights b0,b1,b2 were found by minimizing similarly the maximum absolute value of:
(Δx)NlimΔx→0(b0f(x)+b1f(x+Δx)+b2f(x+2Δx))(1−a)+(c1f(x)+c0f(x+Δx)+c1f(x+2Δx))a−f(x+aΔx)(Δx)N=⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪(b0+b1+b2−1+a(1−b0−b1−b2))f(x)(a−1)(2b0+b1−2)Δxf′(x)(−12a2+(2316−b0)a+b0−1)(Δx)2f′′(x)if N=0,∣∣∣b2=1−b0−b1if N=1,∣∣∣b1=2−2b0if N=2,∣∣∣b0=1516
for 0≤a<1. Use of the prefilter about halves the approximation error and is easier to do than full optimization of the tables.
Figure 4. Approximation error of log2(a) from 11 samples, with and without prefilter and with and without end conditioning. Without end conditioning the prefilter has access to values of the function just outside of the table.
This article probably presents a very similar algorithm: R. Gutierrez, V. Torres, and J. Valls, “FPGA-implementation of atan(Y/X) based on logarithmic transformation and LUT-based techniques,” Journal of Systems Architecture, vol. 56, 2010. The abstract says their implementation beats previous CORDIC-based algorithms in speed and LUT-based algorithms in footprint size.