<jupyter_start><jupyter_text>Clase 4: Errores de redondeo Basado en la sección 1.2 del libro de Burden. El computador representa los números usando un sistema **binario**. Es decir, una lista de 1s y 0s. Las CPUs modernas usan 64 bits, cada uno de los cuales puede ser 0 o 1. Como esto es un número finito de información, la precisión del computador para guardar un número no es infinita y esto puede inducir errores. Para entender el problema, hagamos un ejemplo con 16 bits.<jupyter_code>import numpy as np
pi = np.float16(np.pi)
dos = np.float16(2)
cien = np.float16(100)
cien*np.sin(dos*pi)<jupyter_output><empty_output><jupyter_text>Representación de los números en un sistema binario El computador usa 64 bits para representar un número decimal. Por ejemplo1 11011011111 01100000001010100000000000000000000000000000000000000El primer bit se llama $s$, los siguientes 10 bits se llaman $c$. Los siguientes 52 bits forman una fracción que consiste en la siguiente suma$$f = \sum_{i = 1}^{52}\left(\frac{b_i}{2}\right)^i\,,$$donde $b_i$ es el $i$ bit de esos 52. El número total está dado por$$(-1)^s 2^{c - 1023} (1 + f)\,.$$La fracción de nuestro ejemplo es$$f = \left(\frac{1}{2}\right)^2 + \left(\frac{1}{2}\right)^3 + \left(\frac{1}{2}\right)^{11} +  \left(\frac{1}{2}\right)^{13} + \left(\frac{1}{2}\right)^{15} = 0.375640869140625$$El número $c$ está escrito en binario, lo que quiere decir$$c = \sum_{i = 0}^{10} b_i 2^{10 - i}$$En nuestro ejemplo esto es$$c = 2^{10} + 2^9 + 2^7 + 2^6 + 2^4 + 2^3 + 2^2 + 2^1 + 2^0 = 1759$$Entonces el número escrito arriba corresponde a$$(-1)\times 2^{1759 - 1023} 1.375640869140625 \approx -4.97258\times 10^{221}$$ Hay dos valores de $c$ con significado especial: Todos 0 representa que en la mantisa se toma $2^{-1022}f$ en vez de $2^{-1023}(1 + f)$. Todos $1$ representa $\infty$. * Número más grande de 64 bits (todos 1 excepto $s$ y el último dígito de $c$)0 11111111110 1111111111111111111111111111111111111111111111111111<jupyter_code>f = sum((1/2.)**(np.arange(1,53)))
f
c = sum(2**(np.arange(1,11)))
c - 1023
2**1023*(1+f)<jupyter_output><empty_output><jupyter_text>* Número más pequeño de 64 bits (todos 1 excepto el último dígito de c)1 11111111110 1111111111111111111111111111111111111111111111111111<jupyter_code>-2**1023*(1+f)<jupyter_output><empty_output><jupyter_text>* Número de 64 bits más cercano a cero (todos cero excepto el último dígito de $c$)0 00000000001 0000000000000000000000000000000000000000000000000000<jupyter_code>c  =  1
c - 1023
2**(-1022)<jupyter_output><empty_output><jupyter_text>* Cifras decimales de precisión de 64 bits (todos cero excepto el último dígito de $f$)0 00000000000 0000000000000000000000000000000000000000000000000001<jupyter_code>(1/2)**(52)<jupyter_output><empty_output><jupyter_text>Números de 16 bits Para ilustrar el tipo de dificultades que esto introduce es engorroso trabajar con números tan grandes. Por eso trabajamos con números de 16 bits (como si estuviéramos en los años 90). Un número de este estilo es1 11010 1111000100El primer dígito es $s$, los siguientes cinco forman $c$ y los siguientes diez forman $f$ de manera análoga a la de antes. En nuestro ejemplo tenemos$$f = \left(\frac{1}{2}\right)^1 + \left(\frac{1}{2}\right)^2 + \left(\frac{1}{2}\right)^3 + \left(\frac{1}{2}\right)^4 + \left(\frac{1}{2}\right)^8 = 0.94140625$$$$c = 2^4 + 2^3 + 2^1 = 26$$El número total está dado por $$(-1)^s 2^{c - 15} (1 + f)$$El número de nuestro ejemplo es$$(-1)\times 2^{26 - 15} 1.94140625 = -3976$$ * Número más grande de 16 bits0 11110 1111111111<jupyter_code>f = sum((1/2.)**(np.arange(1,11)))
f
c = sum(2**(np.arange(1,5)))
c
2**(c - 15)*(1 + f)<jupyter_output><empty_output><jupyter_text>* Número más pequeño de 16 bits1 11110 1111111111<jupyter_code>-2**(c-15)*(1 + f)<jupyter_output><empty_output><jupyter_text>* Número más cercano a cero de 16 bits0 00000 0000000001<jupyter_code>2**(1-15)*((1/2)**10)<jupyter_output><empty_output><jupyter_text>* Cifras decimales de precisión de 16 bits0 00000 0000000001<jupyter_code>(1/2)**(10)<jupyter_output><empty_output><jupyter_text>Redondeo, truncación, error absoluto y relativo Para explorar este problema, ignoremos por ahora el sistema *binario* y veamos lo que ocurre en el sistema **decimal**.<jupyter_code>np.pi<jupyter_output><empty_output><jupyter_text>En este caso, si queremos representar el número $\pi$ con una cierta cantidad $n$ de cifras significativas podemos hacer dos cosas: * Truncar: Ignoramos las cifras adicionales, más allá de la $n$-ésima cifra.<jupyter_code>pi_truncado = 3.1415<jupyter_output><empty_output><jupyter_text>* Redondear: Si la cifra $n + 1$ es menor a 5 truncamos, pero si la cifra $n + 1$ es mayor o igual que 5<jupyter_code>pi_redondeado = 3.1416<jupyter_output><empty_output><jupyter_text>Al truncar o redondear cometemos un error. Podemos cuantificar el error de varias maneras: * Error real: estimación - valor verdadero<jupyter_code>pi_truncado - np.pi
pi_redondeado - np.pi<jupyter_output><empty_output><jupyter_text>* Error absoluto: |estimación - valor verdadero|<jupyter_code>np.abs(pi_truncado - np.pi)
np.abs(pi_redondeado - np.pi)<jupyter_output><empty_output><jupyter_text>* Error relativo: |estimación - valor verdadero|/|valor verdadero|<jupyter_code>np.abs(pi_truncado - np.pi)/np.abs(np.pi)
np.abs(pi_redondeado - np.pi)/np.abs(np.pi)<jupyter_output><empty_output><jupyter_text>Aritmética de dígitos finitos Lo que hace el computador es aproximar el resultado de cada operación haciendo una truncación o redondeo. Esto quiere decir que si quiero multiplicar el número $2$ por el número $\pi$, primero aproximo $2 \simeq fl(2)$ y $\pi \simeq fl(\pi)$ (donde $fl$ representa la aproximación de un número dado), y luego de hacer la multiplicación vuelve a aproximar $$2\pi \simeq fl(fl(2)\times fl(\pi))$$. **Hagamos un ejemplo.** Supogamos que queremos calcular lo siguiente$$10000\times(u - v)$$donde $u = 1/5$ y $v = 399/2000$. Sabemos que el resultado debe ser $5$. Veamos qué pasa si usamos números de 16 bits.<jupyter_code>fl_u = np.float16(1)/np.float16(5)
fl_v = np.float16(399)/np.float16(2000)
np.float16(10000)*(fl_u - fl_v)
np.abs(np.float16(10000)*(fl_u - fl_v) - 5)/5<jupyter_output><empty_output><jupyter_text>Esto ocurre porque hay una resta entre dos números muy similares, que luego se multiplica por un número grande. Este tipo de errores ocurre también para los números que usamos de 64 bits, para números más cercanos multiplicados por números más grandes. **Hagamos otro ejemplo.** Busquemos la solución de la ecuación: $$x^2 + 200 x + 1 = 0$$Las soluciones son $$x = \frac{-200 \pm \sqrt{200^2 - 4}}{2}$$<jupyter_code>doscientos = np.float16(200)
dos = np.float16(2)
cuatro = np.float16(4)
discr = np.float16(np.sqrt(np.float16(doscientos**2) - cuatro))
x1 = np.float16(np.float16(-1*doscientos - discr)/dos)
x2 = np.float16(np.float16(-1*doscientos + discr)/dos)
x1
x2
x1v = (-200 - np.sqrt(200**2 - 4))/2
x2v = (-200 + np.sqrt(200**2 - 4))/2
x1v
x2v
x1 - x1v<jupyter_output><empty_output><jupyter_text>Una manera de resolver este problema, es escribir la solución 2 para que no contenga la diferencia de dos números muy similares, esto se logra escribiendo$$\frac{-200 + \sqrt{200^2 - 4}}{2} = \frac{(-200 + \sqrt{200^2 - 4})}{2}\frac{(-200 - \sqrt{200^2 - 4})}{(-200 - \sqrt{200^2 - 4})} = \frac{4}{2(-200 - \sqrt{200^2 - 4})}$$<jupyter_code>np.float16(dos/np.float16(-1*doscientos - discr))<jupyter_output><empty_output><jupyter_text>En general cuando estamos tratando de resolver una ecuación con coeficientes que no tienen el mismo orden de magnitud encontraremos este tipo de problemas. **Otro ejemplo**. Consideremos que queremos evaluar la siguiente función$$P(x) = x^3 - 6.1 x^2 + 3.2 x + 1.5$$en el punto $x = \pi$.<jupyter_code>tres = np.float16(3)
t1 = np.float16(pi**tres)
t2 = np.float16(np.float16(6.1)*np.float16(pi**dos))
t3 = np.float16(np.float16(3.2)*pi)
t4 = np.float16(1.5)
aproximado = np.float16(np.float16(np.float16(t1 - t2) + t3) + t4)
exacto = np.pi**3 - 6.1*np.pi**2 + 3.2*np.pi + 1.5
aproximado
exacto
np.abs(aproximado - exacto)/np.abs(exacto)<jupyter_output><empty_output><jupyter_text>Este error es relativamente grande porque la expresión original contiene un gran número de operaciones y cada operación introduce un error. Podemos reducir este error reduciendo el número de operaciones que tenemos que hacer. Para eso reescribimos la expresión de la siguiente manera$$P(x) = x(x(x - 6.1) + 3.2) + 1.5$$Esta se llama aritmética anidada y reduce el número de operaciones.<jupyter_code>p1 = np.float16(pi - np.float16(6.1))
p2 = np.float(np.float16(pi*p1) + np.float16(3.2))
aproximado = np.float16(np.float16(pi*p2) + np.float16(1.5))
exacto = np.pi**3 - 6.1*np.pi**2 + 3.2*np.pi + 1.5
np.abs(aproximado - exacto)/np.abs(exacto)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1383605072463768, 'fraction_numerical': 0.11118659420289854, 'mean_word_length': 5.391461649782923, 'pattern_counts': {'":': 0, '<': 81, '<?xml version=': 0, '>': 81, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 14, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '20', 'dir': 'jupyter-structured-clean-dedup', 'id': '544508', 'license': 'Apache-2.0', 'n_tokens_mistral': 4088, 'n_tokens_neox': 3405, 'n_words': 943, 'path': 'Clase 04 - Errores de redondeo.ipynb', 'repo_name': 'jorgenorena/Metodos-Numericos', 'size': '8832'}