import sys
sys.float_info
sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)
El sistema decimal es una manera de representar números por una lista de dígitos del conjunto \(\{0, 1, 2, \dots, 9\}\), donde cada dígito representa el coeficiente de una potencia de \(10\).
Ejemplo 3.1 Expansión decimal.
\(4506= 4\cdot 10^3 + 5\cdot 10^2 + 0\cdot 10^1 + 6\cdot 10^0\)
\(158.9= 1\cdot 10^2 + 5\cdot 10^1 + 8\cdot 10^0 +9 \cdot 10^{-1}\)
En este caso, decimos que están representados en base 10, pero se puede generalizar para cualquier base como se enuncia a continuación
Teorema 3.1 Sea \(a\in\mathbb{Z}\) con \(a>1\), entonces cada \(n\in \mathbb{N}\) se puede expresar de manera única como
\[\begin{equation} n = r_ka^k+ \cdots + r_1 a+ r_0 a^0 \end{equation}\]
donde \(0\leq r_i< a\) para \(i=0, \cdots, k\) y \(r_k\neq 0\).
Decimos que la representación de un número natural \(n\) en base a es \(r_kr_{k-1}\cdots r_1r_0\) si \(n = r_ka^k+ \cdots + r_1 a+ r_0 a^0\) y lo denotamos \(n=(r_kr_{k-1}\cdots r_1r_0)_a\).
Ejemplo 3.2 Para el número natural \(4506\) en base \(5\) será \(121011\) ya que
\[\begin{equation} 4506= 1\cdot 5^5 + 2\cdot 5^4 + 1\cdot 5^3 + 0\cdot 5^2 + 1\cdot 5^1 + + 1\cdot 5^0 \end{equation}\]
y entonces se tendrá que \(4506=(4506)_{10}=(121011)_5\).
Una representación muy importante de los números para las computadoras es la base 2 o números binarios. En binario los únicos dígitos disponibles son \(0\) y \(1\), y cada dígito es el coeficiente de una potencia de \(2\). Los dígitos de un número binario también se conocen como bit. Con los números binarios es posible realizar operaciones aritméticas de manera análoga como se lleva a cabo en base \(10\).
Ejemplo 3.3 Convertir el número \((25)_{10}\) a binario.
\[\begin{equation} (25)_{10}= 16 + 8 + 1 =1\cdot 2^4 + 1\cdot 2^3 + 0\cdot 2^2 + 0\cdot 2^1 + 1\cdot 2^0 =(11001)_2 \end{equation}\]
Ejemplo 3.4 Convertir \((37)_{10}\) y \((17)_{10}\) en binario. Posteriormente, sumar y multiplicar los números resultantes en binario. Verificar que el resultado sea correcto en base 10.
Ejercicio 3.1 Escribir una función llamada my_bin_2_dec(b)
donde \(b\) es un número binario representado por una lista de 1’s y 0’s. El último elemento de \(b\) representa el coeficiente de \(2^0\), el penúltimo elemento de \(b\) representa el coeficiente de \(2^1\) y así sucesivamente. El resultado \(d\), debe ser la representación decimal de \(b\). Comparar los resultados con la función int(numero, 2)
de Python.
Ejercicio 3.2 Escribir una función llamada my_dec_2_bin(d)
donde \(b\) es un número entero positivo en base 10. El resultado, \(b\), debe ser una lista de 1’s y 0’s. Comparar los resultados con la función bin()
de Python.
Los números binarios son útiles para las computadoras porque las operaciones aritméticas en los dígitos 0 y 1 se pueden representar usando AND, OR y NOT, lo que las computadoras pueden hacer extremadamente rápido.
A diferencia de los humanos, que pueden abstraer números con valores arbitrariamente grandes, las computadoras tienen un número fijo de bits que son capaces de almacenar a la vez. Por ejemplo, una computadora de 32 bits puede representar y procesar números binarios de 32 dígitos y no más. Si se usan todos los 32 bits para representar números binarios enteros positivos, esto significa que hay
\[\begin{equation} \sum_{n=0}^{31} 2^n =4,294,967,296 \end{equation}\]
números que la computadora podría representar. Esta no es una cantidad muy grande de números en absoluto y sería completamente insuficiente para hacer cualquier aritmética útil. Por ejemplo, no se puede calcular la suma perfectamente razonable \(0.25+1.25\) utilizando esta representación porque todos los bits están dedicados sólo a números enteros.
El número de bits suele ser fijo para cualquier computadora. El uso de la representación binaria nos da un rango y una precisión de números insuficientes para hacer cálculos relevantes. Para lograr el rango de valores necesarios con el mismo número de bits, usamos números de punto flotante o float para abreviar.
En 1985, el Instituto para Ingenieros Eléctricos y Electrónicos (IEEE; Institute for Electrical and Electronic Engineers) publicó un reporte llamado Binary Floating Point Arithmetic Standard 754-1985 (Estándar para la aritmética binaria de punto flotante). En 2008 se publicó una versión actualizada con el nombre IEEE 754-2008; la cual proporciona estándares para números de punto flotante decimales y binarios, formatos para intercambio de datos, algoritmos para redondear operaciones aritméticas y manejo de excepciones.
Una representación de 64 bits (dígito binario) se usa para un número real. El primer bit es un indicador de signo, denominado \(s\). A éste le sigue un exponente de 11 bits, \(c\), llamado característica, y una fracción binaria de 52 bits, \(f\), llamada mantisa. La base para el exponente es 2.
Puesto que los 52 dígitos binarios corresponden entre 16 y 17 dígitos decimales, podemos suponer que un número representado en este sistema tiene por lo menos 16 dígitos de precisión. El exponente de 11 bits provee un rango de 0 a \(2^{11}-1=2047\). Sin embargo, usar sólo enteros positivos para el exponente no permitiría una representación adecuada de los números con magnitud pequeña. Para garantizar que estos números son igualmentes representables, 1023 se resta a la característica, por lo que el rango del exponente en realidad se encuentra entre -1023 y 1024.
Para ahorrar almacenamiento y proporcionar una representación única para cada número de punto flotante, se impone una normalización. Por medio de este sistema se obtiene un número de punto flotante de la forma
\[\begin{equation} n = (-1)^s 2^{c-1023} (1+f) \end{equation}\]
En Python podemos obtener la información flotante usando el paquete sys
como se muestra a continuación:
import sys
sys.float_info
sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)
La estructura sys.float_info
en Python proporciona información sobre la representación de números en punto flotante en la máquina, siguiendo el estándar IEEE 754. El significado de cada atributo se muestra a continuación:
max
:
max_exp
:
max_10_exp
:
min
:
min_exp
:
min_10_exp
:
dig
:
mant_dig
:
epsilon
:
radix
:
rounds
:
Ejemplo 3.5 ¿Cuál es el número
1 10000000010 10000000000000000000000000000000000000000000000000 (IEEE754) en base10?
La exponente en decimal es \(1\cdot 2^{10}+ 1\cdot 2^1 - 1023=3\). La mantisa es
\[\begin{equation} 1\cdot \frac{1}{2^1} + 0\cdot \frac{1}{2^2}\cdots =0.5 \end{equation}\]
Por lo tanto
\[\begin{equation} n= (-1)^1\cdot 2^3\cdot (1+0.5)=-12 \end{equation}\]
Ejemplo 3.6 ¿Cómo se representa el 15.0 en IEEE754? ¿Cuál es el número más grande menor que 15.0? ¿Cuál es el número más pequeño mayor que 15.0?
15 (base10) = 0 10000000010 111000000000000000000000000000000000000000000000 (IEEE754)
El siguiente número más pequeño es
0 10000000010 1101111111111111111111111111111111111111111111111111 =14.99999999999999982236431605997
El siguiente número más grande es
0 10000000010 1110000000000000000000000000000000000000000000000001 =15.00000000000000017763568394003
Por lo tanto, el número IEEE754
0 10000000010 111000000000000000000000000000000000000000000000000
no solo representa el número 15.0, sino también todos los números reales entre sus vecinos inmediatos. Por lo tanto, a cualquier cálculo que tenga un resultado dentro de este intervalo se le asignará 15.0.
Se denomina brecha o gap a la distancia de un número a otro. Debido a que la fracción se multiplica por \(2^{c-1023}\), la brecha crece a medida que crece el número representado. La brecha en un número dado se puede calcular usando la función spacing
en numpy
.
Ejemplo 3.7 Utilizar la función de spacing
para determinar la brecha en \(1e9\). Verifique que agregar un número a \(1e9\) que sea menos de la mitad de la brecha en \(1e9\) da como resultado el mismo número.
import numpy as np
1e9) np.spacing(
np.float64(1.1920928955078125e-07)
1e9 == (1e9 + np.spacing(1e9)/3)
np.True_
Los números que son mayores que el número de punto flotante representable más grande dan como resultado un desbordamiento, Python maneja este caso asignando el resultado a inf
. Los números que son más pequeños que el mínimo representable dan como resultado un subdesbordamiento, Python maneja este caso asignando el resultado a 0.
Si se suma el número flotante máximo de 64 bits con 2, se obtiene el mismo número. El punto flotante de Python no tiene la precisión suficiente para almacenar el sys.float_info.max
+2, por lo tanto, las operaciones son esencialmente equivalentes a sumar cero.
max + 2 == sys.float_info.max sys.float_info.
True
Al agregar el número flotante máximo de 64 bits a sí mismo da como resultado un desbordamiento y Python asigna este número de desbordamiento a inf
.
max + sys.float_info.max sys.float_info.
inf
Entonces, ¿cuál es la diferencia al usar el estándar IEEE754 frente al sistema binario? El uso de binario de 64 bits nos da \(2^{64}\) números. Dado que el número de bits no cambia entre binario y IEEE754, también IEEE754 debe darnos \(2^{64}\) números. En binario, los números tienen un espaciado constante entre ellos. Como resultado, no puede tener tanto rango (es decir, gran distancia entre los números mínimo y máximo que se pueden representar) y precisión (es decir, pequeño espaciado entre números). El estándar IEEE754 supera esta limitación mediante el uso de una precisión muy alta en números pequeños y una precisión muy baja en números grandes. Esta limitación suele ser aceptable porque la diferencia entre números grandes sigue siendo pequeña en relación con el tamaño del propio número. Por lo tanto, incluso si la brecha es de millones, es irrelevante para los cálculos normales si el número bajo consideración es de billones o más.
Al representar los números de punto flotante en las computadoras como fracciones de base 2 tiene el efecto secundario de que los números no se pueden almacenar con una precisión perfecta, sino que los números se aproximan por un número finito de bytes. Por lo tanto, la diferencia entre una aproximación de un número utilizado en el cálculo y su valor correcto (verdadero) se denomina error de redondeo.
El otro es el error de truncamiento, que se presentará posteriormente. La diferencia es que el error de truncamiento es el error que se comete al truncar una suma infinita y aproximarla mediante una suma finita.
El error de redondeo más común es el error de representación en los números punto flotante. Un ejemplo sencillo será representar \(\pi\). Sabemos que es un número infinito, pero cuando lo usamos, generalmente solo usamos dígitos finitos. Por ejemplo, si solo usa 3.14159265, habrá un error entre esta aproximación y el número infinito verdadero. Otro ejemplo será 1/3, el valor verdadero será 0.3333333333…, por muchos dígitos decimales que elijamos, también hay un error de redondeo.
Además, cuando redondeamos los números varias veces, el error se acumulará. Por ejemplo, si 4.845 se redondea a dos decimales, es 4.85. Luego, si lo redondeamos de nuevo a un decimal, es 4.9, el error total será 0.55. Pero si solo redondeamos una vez a un decimal, es 4.8, donde el error es 0.045.
En el ejemplo anterior, el error entre 4.845 y 4.8 debería ser 0.055. Pero si lo calculamos en Python, tendremos que 4.9-4.845 no es igual a 0.055
4.9-4.845 == 0.055
False
¿Por qué sucede esto? Veamos cual es el resultado de 4.9-4.845:
4.9-4.845
0.055000000000000604
Esto se debe a que el punto flotante no se puede representar con el número exacto, es sólo una aproximación, y cuando se usa en aritmética, está causando un pequeño error.
Otro ejemplo muestra a continuación que 0.1 + 0.2 + 0.3 no es igual a 0.6, que tiene la misma causa.
0.1+0.2+0.3 == 0.6
False
0.1+0.2+0.3
0.6000000000000001
Aunque los números no se pueden hacer más cerca de sus valores exactos previstos, la función round
puede ser útil para el redondeo posterior, de modo que los resultados con valores inexactos sean comparables entre sí:
round(0.1 + 0.2 + 0.3, 5) == round(0.6, 5)
True
Cuando estamos haciendo una secuencia de cálculos en una entrada inicial con error de redondeo debido a una representación inexacta, los errores pueden acumularse. El siguiente es un ejemplo, tenemos el número 1 sumando y restando 1/3, lo que nos da el mismo número 1.
1+1/3-1/3
1.0
Pero, ¿qué pasa si sumamos 1/3 muchas veces y restamos el mismo número de veces 1/3, seguimos obteniendo el mismo número 1?
def suma_y_resta(iteraciones):
= 1
resultado for i in range(iteraciones):
+= 1/3
resultado for i in range(iteraciones):
-= 1/3
resultado return(resultado)
5) suma_y_resta(
1.0
100) suma_y_resta(
1.0000000000000002
1000) suma_y_resta(
1.0000000000000064