2.2. Números decimales (tipo float): la precisión#

2.2.1. Números fraccionarios#

Los números fraccionarios son los que se obtienen por cociente de dos enteros. Estos números reciben el nombre de racionales. En Python son objetos de tipo float. Pueden ser de tres tipos:

  • Exactos: con un número finto de decimales: 0.1

  • Periódicos puros: con infinitos decimales que se repiten desde el principio: 0.333\(\dots\) ó 7.535353535353\(\dots\)

  • Periódicos mixtos: con infinitos decimales que se repiten a patir de un cierto decimal: 7.4533333\(\dots\) ó 4.1264567567567\(\dots\)

Los ejemplos que hemos puestos corresponden a la representación decimal, también llamada, representación en base 10. Existen más bases en las que se pueden representar los números, como la base 2.

En base 10 se tiene que

  • Números exactos: aquellos números fraccionarios cuyos denominadores sólo contienen los factores 2 y/o 5

  • Números periódicos puros: los números fraccionarios cuyos denominadores no contienen ni el factor 2 ni el 5

  • Números periódicos mixtos: los números fraccionarios cuyos denominadores contienen otros factores aparte del 2 y/o el 5

Obsérvese que un número fraccionario será exacto o periódico según la representación que se utilice, es decir, según la base en la que está escrito o representado. En base 2 son fracciones exactas las fracciones con potencias de dos en el denominador.

Ejemplo del problema al que nos enfrentamos

suma = 0

for contador in range(10):
    suma += 1/10

print('Al sumar diez veces 1/10 obtengo: ' + str(suma))
print('Resulta que ' + str(suma) + ' no es igual a 1.0')
print(str(suma == 1.0))
Al sumar diez veces 1/10 obtengo: 0.9999999999999999
Resulta que 0.9999999999999999 no es igual a 1.0
False

Los ordenadores son entes finitos: manejan un número finito de decimales.

Algunos números fraccionarios (cocientes de números enteros) tienen infinitos decimales periódicos. Por ejemplo,

\[1/6 = 0.16666666666666666 \dots\]

Como tenemos un dos en el denominador es un peródico mixto. Al manejar un número finito de decimales, que es lo que la computadora almacena, dejamos de almacenar el número fraccionario original. Imaginemos que la computadora almacena solamente cuatro cifras decimales. Entonces, dado el número \(1/6\) la computadora almacena en memoria el número \(0.1666\), pero

\[ 0.1666 \not= 1/6.\]

En consecuencia, se obtiene que al sumar seis veces un sexto: $\( 0.1666 + 0.1666 + 0.1666 + 0.1666 + 0.1666 + 0.1666 = 0.9996 \not= 1 = 1/6+1/6+1/6+1/6+1/6+1/6 \)$

Veamos esto:

suma = 0

for contador in range(6):
    suma += 1/6

print('Al sumar seis veces 1/6 obtengo: ' + str(suma))
print('Resulta que ' + str(suma) + ' no es igual a 1.0')
print(str(suma == 1.0))
Al sumar seis veces 1/6 obtengo: 0.9999999999999999
Resulta que 0.9999999999999999 no es igual a 1.0
False

Sin embargo, al sumar dos veces \(1/2\) 0 cuatro veces \(1/4\) que obtenemos que la suma es igual a 1:

suma = 0

for contador in range(4):
    suma += 1/4

print('Al sumar cuatro veces 1/4 obtengo: ' + str(suma))
print('Resulta que ' + str(suma) + ' sí es igual a 1.0')
print(str(suma == 1.0))
Al sumar cuatro veces 1/4 obtengo: 1.0
Resulta que 1.0 sí es igual a 1.0
True

¿Qué está pasando?

Los ordenadores hacen los cálculos en base \(\textbf{2}.\) Y en esa base \(1/4\) tiene un número finito de decimales porque el denominador es una potencia de \(2.\) Esto no ocurre con \(1/10\) o \(1/6;\) estos números tienen factores diferenctes de \(2\) en el denominador y por lo tanto no tiene un número finito de decimales en base \(2.\)

En conclusión:

No se puede utilizar una condición de igualdad (==) para hacer una comprobación de igualdad de números decimales (float). Es mejor establecer que la diferencia es pequeña. Así aparece la noción de precisión

2.2.2. La precisión#

Cuando queremos saber si dos números decimales (float) \(N_1\) y \(N_2\) son iguales, el operador de comparación == puede fallar. Por ejemplo, si \(N_1=1\) y \(N_2=1/6+1/6+1/6+1/6+1/6+1/6\), es claro que \(N_1=N_2\) pero al hacerlo computacionalmente obtenemos un resultado diferente:

N1 = 1
N2 = 0
den = 6

for i in range(den):
    N2+=1/den

print(N1)
print('')
print(N2)
print('')
print(N1==N2)
1

0.9999999999999999

False

Por ello, es necesario disponer de otro método de comparación de números decimales. Usaremos el concepto de precisón. Diremos que dos números decimales son iguales con una precisión \(\varepsilon\) dada, si

\[ \left|N_1-N_2\right|<\varepsilon,\qquad \varepsilon>0. \]

Dicho de otro modo: si la distancia entre \(N_1\) y \(N_2\) es menor que \(\varepsilon,\) consideremos que son el mismo número. En el ejemplo anterior, para dos precisiones distintas podemos obtener resultados de “igualdad” distintos para los mismos \(N_1\) y \(N_2\):

distancia = abs(N1-N2)

epsilon1 = 0.001
epsilon2 = 0.000000000000000001

print('Distancia:', distancia)
print('')
print('epsilon1:', epsilon1)
print('')
print('epsilon1:', epsilon2)
Distancia: 1.1102230246251565e-16

epsilon1: 0.001

epsilon1: 1e-18

Observación: hemos cambiado el operador de comparación == por <. Estamos usando que la distancia entre dos números es un conjunto ordenado.

2.2.3. Ejercicios#

  1. Escribir una función que devuelva una cadena que sea la representación en base \(2\) de un número entero no negativo en base \(10.\)

  2. Escribir una función que devuelva el entero correspondiente a la suma de los dígitos de la representación en base \(5\) de un entero no negativo en base \(10.\)