Construcción de una calculadora II

Contenidos

Introducción

Continuamos con la construcción de la calculadora añadiéndole el seno, el coseno y la función exponencial.

Mientras para la primera parte de la calculadora (calculadoraNewton.py) hemos usado el método de Newton para calcular raíces de ecuaciones no lineales, en esta parte usaremos la fórmula de Taylor para aproximar las funciones.

La condición sigue siendo que solo se pueden usar sumas y multiplicaciones. Sin embargo, como ya tenemos la función inverso definida en nuestra librería libCalculadora.py, podemos usarla para calcular divisiones.

El esquema de esta práctica es la siguiente:

  • Añadir las funciones nuevas a libCalculadora.py, y probarlas. Esto lo podemos hacer utilizando Spyder.
  • Ampliamos calculadoraNewton.py a calculadoraNewtonTaylor.py, añadiendo a Qt nuevo los botones y las funciones.
  • Probamos la calculadora desde línea de comandos.

Añadiendo las funciones seno y coseno

Empezamos con el seno. El coseno es parecido.

Calcular $\mathrm{sen}(x)$ para un $x \in \mathbb{R}$ se puede reducir a calcularlo para un $y\in [0,2\pi)$ porque $\mathrm{sen}(x)$ es una función periódica de periodo $2\pi$, es decir, $\mathrm{sen}(x+2\pi) = \mathrm{sen}(x)$.

Por lo tanto, dado un número $x$, primero le sumamos o restamos $2k\pi$ a $x$ para conseguir $y=x\pm 2k\pi \in [0,2\pi)$, con $k\in\mathbb{Z}$. Por ejemplo, si $x= 23.43$ tenemos $y = x - 2k\pi = 23.43- 6\pi = 4.580444...\in [0,2\pi)$, con $k=3$.

Por otra parte, como $\mathrm{sen}(x)$ es una función impar, se verifica $\mathrm{sen}(-x) = - \mathrm{sen}(x)$, y sólo hay que calcular el seno para valores positivos guardando el signo para añadírselo al final. Tener en cuenta que el coseno es par y entonces $\cos(-x) = \cos(x)$.

In [3]:
import numpy as np
import matplotlib.pyplot as plt
In [4]:
%matplotlib inline
In [5]:
y=np.linspace(0,2*np.pi)   
plt.plot(y,np.sin(y));
plt.title('seno');
# ponemos ticks solo en algunos puntos del eje X
xt = [0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi]
labels = ['0',r'$\pi/2$',r'$\pi$',r'$3\pi/2$',r'$2\pi$']
plt.xticks(xt, labels );

Si se mira el dibujo, se ve que los valores del seno $\mathrm{sen}(y)$ se repiten, también en los intervalos $[0,\pi/2], ~[\pi/2,\pi], ~[\pi,3\pi/2], ~[3\pi/2,2\pi]$.

Por lo tanto, es suficiente con calcular los valores del seno en el intervalo $[0,\pi/2]$.

Podemos encontrar $j\in\{0,1,2,3\}$ tal que $z = y - j \pi/2 \in [0,\pi/2]$. Entonces tendremos los casos siguientes:

  • $j=0$: Y calculamos la aproximación de $\mathrm{sen}(y)$.
  • $j=1$: Y se tiene que $\mathrm{sen}(y) = \mathrm{sen}(\frac{\pi}{2}-z)$.
  • $j=2$: Y se tiene que $\mathrm{sen}(y) = - \mathrm{sen}(z)= \mathrm{sen}(-z)$.
  • $j=3$: Y se tiene que $\mathrm{sen}(y) = -\mathrm{sen}(\frac{\pi}{2}-z)= \mathrm{sen}(z-\frac{\pi}{2})$.

¿Por qué hacemos esta redución? La razón es que vamos a usar la fórmula de Taylor que para $y_0\in[0,2\pi]$,

$$ \mathrm{sen}(y) \approx \mathrm{sen}(y_0) + \cos(y_0) (y-y_0) - \frac{\mathrm{sen}(y_0)}{2!} (y-y_0)^2 - \frac{\cos(y_0)}{3!} (y-y_0)^3 +\cdots $$

Debemos escoger $y_0$ de forma que conozcamos el valor exacto de $\mathrm{sen}(y_0)$ y $\cos(y_0)$. Por ejemplo, $y_0=0$. En este caso, tenemos

$$ \mathrm{sen}(y) \approx y - \frac{1}{6} y^3 + \cdots $$

y sólo utilizamos sumas y multiplicaciones.

Como el error de la aproximación aumenta cuando los puntos $y_0$ e $y$ se alejan, queremos que $|y-y_0|$ sea lo más pequeño posible. Las fórmulas de arriba nos permiten mantener siempre una distancia menor que $\pi/2$. Si no la usamos la distancia puede llegar a ser $2\pi$, aumentando el error de aproximación.

Otra posibilidad sería usar otro $y_0$ para cada intervalo de las $y$. Por ejemplo, $y_0=\pi/2,~\pi$, o $3\pi/2$. Pero esto implicaría que tendríamos que recalcular los coeficientes de la fórmula de Taylor.

Vamos a usar la fórmula de Taylor de orden $20$. El vector que contiene los coeficientes es

In [6]:
p_seno = [0.0, 1.0, 0.0, -0.166666666666667, 0.0, 0.00833333333333333, 0.0,
         -0.000198412698412698, 0.0, 2.75573192239859e-6, 0.0,
         -2.50521083854417e-8, 0.0, 1.60590438368216e-10, 0.0,
         -7.64716373181982e-13, 0.0, 2.81145725434552e-15, 0.0,
         -8.22063524662433e-18, 0.0]

El valor del polinomio que tiene estos coeficientes en un punto $x$

In [7]:
x = np.pi/2
z = np.polyval(p_seno[::-1], x )   # Damos la vuelta al vector de coeficientes
print z
1.0

Como vas a necesitar las aproximaciones de $\pi/2$, $\pi$, etc., usa

In [8]:
piMedios  = 1.5707963267948966
pi        = 3.141592653589793
pi3Medios = 4.71238898038469
dosPi     = 6.283185307179586

Para el coseno

In [9]:
p_coseno = [1.0, 0.0, -0.5, 0.0, 0.0416666666666667, 0.0, -0.00138888888888889,
         0.0, 2.48015873015873e-5, 0.0, -2.75573192239859e-7, 0.0,
         2.08767569878681e-9, 0.0, -1.14707455977297e-11, 0.0,
         4.77947733238739e-14, 0.0, -1.56192069685862e-16, 0.0,
         4.11031762331216e-19]

x = np.pi/2
z = np.polyval(p_coseno[::-1], x )
print z
3.33066907388e-16

Añadiendo la función exponencial

El caso de la función exponencial es más sencillo.

Primero, tenemos que $e^{-x} = \displaystyle\frac{1}{e^x}$, por lo tanto es suficiente con calcular los exponenciales de números positivos, seguido por la división, en caso de que el número original sea negativo.

Segundo, tenemos que $e^{x_1+x_2} = e^{x_1} e^{x_2}$. Por lo tanto, para calcular $e^x$, descomponemos $x$ en la suma de su parte entera, $m$, y su parte fraccionaria, $y$. Es decir, $x = y+m$ y entonces $e^x = e^y e^m$.

Por ejemplo, para calcular $e^{3.21}$ descomponemos $3.21 = 0.21 + 3$ y entonces $e^{3.21} = e^{0.21} e^3$. Obviamente, $e^3 = e*e*e$. El número $e$ debe introducirse como una constante que aproxime su valor verdadero. Se puede usar

In [10]:
e = 2.7182818284590451

Finalmente, debemos calcular $e^y$ (la parte no entera). Para hacer esto, usaremos la fórmula de Taylor centrada en $x_0=0$

$$ e^y = \sum_{n=0}^{n=\infty} \frac{1}{n!}x^n. $$

Como en el caso del seno y el coseno utilizaremos 20 coeficientes

In [11]:
p_exp = [1.0, 1.0, 0.5, 0.166666666666667, 0.0416666666666667, 
         0.00833333333333333, 0.00138888888888889, 0.000198412698412698, 
         2.48015873015873e-5, 2.75573192239859e-6, 2.75573192239859e-7, 
         2.50521083854417e-8, 2.08767569878681e-9, 1.60590438368216e-10, 
         1.14707455977297e-11, 7.64716373181982e-13, 4.77947733238739e-14, 
         2.81145725434552e-15, 1.56192069685862e-16, 8.22063524662433e-18, 
         4.11031762331216e-19]
x = 1
z = np.polyval(p_exp[::-1], x )
print z
2.71828182846

Finalizando la calculadora

Una vez que tenemos las nuevas funciones:

  • Ampliamos calculadoraNewton.py a calculadoraNewtonTaylor.py, añadiendo al Qt los botones y las funciones nuevos.

  • Probamos la nueva calculadora en línea de comandos: comparar los resultados con los de la calculadora en el menú de accesorios.