Puedes usar logaritmos para deshacerte de la división. Para (x,y) en el primer cuadrante:
z=log2(y)−log2(x)atan2(y,x)=atan(y/x)=atan(2z)
Figura 1. Parcela de atan(2z)
atan(2z) aproximar atan ( 2 z ) en el rango −30<z<30 para obtener la precisión requerida de 1E-9. Puede aprovechar la simetría atan(2−z)=π2−atan(2z)o alternativamente asegúrese de que(x,y)esté en un octante conocido. Para aproximar ellog2(a):
b=floor(log2(a))c=a2blog2(a)=b+log2(c)
b se puede calcular encontrando la ubicación del bit distinto de cero más significativo. c puede calcularse mediante un cambio de bit. Debería aproximar ellog2(c) en el rango1≤c<2 .
Figura 2. Gráfico del log2(c)
214+1=16385log2(c)30×212+1=122881atan(2z)0<z<30z
atan(2z)zz0≤z<1floor(log2(z))=0
atan(2z)0≤z<1floor(log2(z))z≥1atan(2z)z0≤z<32
Para referencia posterior, aquí está el script torpe de Python que usé para calcular los errores de aproximación:
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])
f(x)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,
donde es la segunda derivada de y está en un máximo local del error absoluto. Con lo anterior obtenemos las aproximaciones:f′′(x)f(x)x
atanˆ(2z)−atan(2z)≈(Δz)22z(1−4z)ln(2)28(4z+1)2,log2ˆ(a)−log2(a)≈−(Δa)28a2ln(2).
Debido a que las funciones son cóncavas y las muestras coinciden con la función, el error siempre es en una dirección. El error absoluto máximo local podría reducirse a la mitad si el signo del error se alternara una y otra vez cada intervalo de muestreo. Con la interpolación lineal, se pueden lograr resultados casi óptimos filtrando previamente cada tabla de la siguiente manera:
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,
donde e son la tabla original y la filtrada que abarcan y los pesos son . El acondicionamiento final (primera y última fila en la ecuación anterior) reduce el error en los extremos de la tabla en comparación con el uso de muestras de la función fuera de la tabla, porque la primera y la última muestra no necesitan ajustarse para reducir el error de la interpolación entre él y una muestra justo afuera de la mesa. Las subtablas con diferentes intervalos de muestreo deben prefiltrarse por separado. Los valores de los pesos se encontraron minimizando secuencialmente para aumentar el exponentexy0≤k≤Nc0=98,c1=−116,b0=1516,b1=18,b2=−116c0,c1N El valor absoluto máximo del error aproximado:
(Δ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
para las posiciones de interpolación entre muestras , con una función cóncava o convexa (por ejemplo ). Con esos pesos resueltos, los valores de los pesos de acondicionamiento final se encontraron minimizando de manera similar el valor absoluto máximo de:0≤a<1f(x)f(x)=exb0,b1,b2
(Δ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
para . El uso del prefiltro acerca de la mitad del error de aproximación y es más fácil de hacer que la optimización completa de las tablas.0≤a<1
Figura 4. Error de aproximación de de 11 muestras, con y sin prefiltro y con y sin acondicionamiento final. Sin el acondicionamiento final, el prefiltro tiene acceso a los valores de la función justo fuera de la tabla.log2(a)
Este artículo probablemente presenta un algoritmo muy similar: R. Gutiérrez, V. Torres y J. Valls, " Implementación FPGA de atan (Y / X) basada en transformación logarítmica y técnicas basadas en LUT " , Journal of Systems Architecture , vol . 56, 2010. El resumen dice que su implementación supera los algoritmos anteriores basados en CORDIC en velocidad y los algoritmos basados en LUT en tamaño de huella.