Python Módulo 2 – Valores, variables, números y cadenas
Sorpresas del punto flotante y cómo manejarlas
Por qué 0.1 + 0.2 no es 0.3 en Python, cómo se guardan los floats en binario y cómo compararlos, sumarlos y redondearlos con math.isclose, fsum y Decimal.
Qué vas a aprender
- Explicar por qué 0.1 + 0.2 != 0.3 con la representación binaria
- Predecir dónde deja un float de guardar números enteros exactos, y qué pasa más allá de su mayor valor
- Comparar floats de forma segura con math.isclose, y detectar NaN e infinito
- Sumar muchos floats con precisión y convertir un resultado float en un número entero sin perder una unidad
- Elegir entre float, decimal.Decimal y fractions.Fraction
Antes de empezar
Qué vas a aprender · Antes de empezar
Qué vas a aprender
- Explicar por qué 0.1 + 0.2 != 0.3 con la representación binaria
- Predecir dónde deja un float de guardar números enteros exactos, y qué pasa más allá de su mayor valor
- Comparar floats de forma segura con math.isclose, y detectar NaN e infinito
- Sumar muchos floats con precisión y convertir un resultado float en un número entero sin perder una unidad
- Elegir entre float, decimal.Decimal y fractions.Fraction
Antes de empezar
En esta página
Pide a Python 0.1 + 0.2 y responde 0.30000000000000004. No hay nada roto: C, Java y casi todos los demás lenguajes guardan esos números igual y obtienen el mismo resultado. En cuanto sabes lo que guarda de verdad un float, las sorpresas se vuelven previsibles, y unos pocos hábitos las mantienen fuera de tus resultados.
Vas a ver el valor exacto detrás de 0.1, seguir 0.1 + 0.2 desde el código hasta la respuesta impresa y adquirir los hábitos que mantienen honrados a los floats: comparar con una tolerancia, sumar con fsum(), redondear en lugar de truncar y elegir Decimal o Fraction cuando un float es el tipo equivocado.
Un float guarda la fracción binaria más cercana
En casi todas las computadoras, un float de Python es un número de “doble precisión” IEEE 754: El estándar de aritmética de punto flotante que siguen casi todas las computadoras. Un float de Python es su doble de 64 bits, una fracción binaria con 53 bits significativos y un exponente.: una fracción binaria con 53 bits significativos y un exponente. Las cifras binarias después del punto representan mitades, cuartos, octavos, etc., así que una fracción solo se puede guardar exacta si es una suma de esas piezas, lo que significa que su denominador, en su forma irreducible, es una potencia de 2. 0.75 es ½ + ¼ y es exacto. 0.1 es 1/10, y 10 tiene un factor 5, así que en binario nunca termina: 0.000110011001100… El float conserva los 53 primeros bits significativos y redondea el resto.
from decimal import Decimal
print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
print(0.5 + 0.25 == 0.75) # halves and quarters are exact in binary
print(Decimal(0.1)) # the exact value stored for 0.1
print(Decimal(0.2))
print(Decimal(0.3))
print((0.1).as_integer_ratio()) # the same stored value as a fraction
print(2 ** 55) Salida
0.30000000000000004 False True 0.1000000000000000055511151231257827021181583404541015625 0.200000000000000011102230246251565404236316680908203125 0.299999999999999988897769753748434595763683319091796875 (3602879701896397, 36028797018963968) 36028797018963968
Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 stored_value.py
Se ejecuta en este dispositivo, en tu navegador. La primera ejecución descarga Python (unos 13.5 MB), que se guarda para las siguientes.
Tu ejecución, en este navegador
Convertir un float a Decimal muestra todas las cifras del valor que guarda. El float de 0.1 es un poco más que 0.1, el de 0.2 un poco más que 0.2, y el de 0.3 un poco menos que 0.3. Como fracción, as_integer_ratio() muestra que el 0.1 guardado es 3602879701896397 dividido entre 2 elevado a 55. Cuando Python imprime un float elige el decimal más corto que se guardaría como el mismo float, y por eso print(0.1) muestra solo 0.1 y el error queda oculto hasta que un cálculo lo saca a la vista.
Más de cerca: por qué la suma falla
Entre dos floats vecinos no hay ningún otro float, así que cada resultado tiene que caer en uno de ellos. La suma de los 0.1 y 0.2 guardados cae exactamente a medio camino entre los dos floats más cercanos a 0.3:
import math
from fractions import Fraction
exact_sum = Fraction(0.1) + Fraction(0.2) # the sum of the two stored values, exactly
below = Fraction(0.3) # the float that 0.3 is stored as
above = Fraction(math.nextafter(0.3, 1)) # the next float up
print(below < exact_sum < above)
print(exact_sum - below == above - exact_sum) # exactly halfway
print((0.3).hex(), (0.1 + 0.2).hex()) # the last digits: 3 is odd, 4 is even Salida
True True 0x1.3333333333333p-2 0x1.3333333333334p-2
Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 halfway.py
Se ejecuta en este dispositivo, en tu navegador. La primera ejecución descarga Python (unos 13.5 MB), que se guarda para las siguientes.
Tu ejecución, en este navegador
Por qué 0.1 + 0.2 cae en el float por encima de 0.3
Descripción en texto del diagrama
Un tramo corto de la recta numérica, dibujado en vertical con los valores mayores más arriba, muestra dos floats vecinos, los más cercanos a 0.3. No hay ningún float entre ellos.
- El de abajo, 0.29999999999999998889…, es el float en el que se guarda 0.3: es el float más cercano a 0.3, que queda un poco por encima.
- El de arriba, 0.30000000000000004441…, es el siguiente float hacia arriba.
- La suma exacta de los valores guardados de 0.1 y 0.2 queda exactamente a medio camino entre los dos. La suma tiene que redondearla a uno de ellos, y un empate va al float cuya última cifra binaria es par, que aquí es el de arriba: una flecha sube la suma hasta él.
Así que 0.1 + 0.2 da el float de arriba, y 0.3 se guarda como el de abajo: dos floats distintos, y por eso 0.1 + 0.2 == 0.3 es False.
En un empate, el hardware redondea al float cuya última cifra binaria es par, la regla por defecto de IEEE 754 y la misma regla de “redondeo al par: La regla que sigue round() con los empates. Un valor exactamente a medio camino entre dos números enteros va al par, así que round(2.5) es 2 y round(3.5) es 4. Más en “Enteros, floats y operadores aritméticos” (en inglés)” que usa round(). Las formas hexadecimales impresas arriba terminan en 3 y en 4, y la par es la del float de arriba. 0.3 escrito en tu programa se guarda como el de abajo, el float más cercano a 0.3. Dos floats distintos, así que == dice False. Sigue el recorrido completo de la suma, etapa por etapa, desde los dos literales de tu código hasta la respuesta que imprime Python:
El dibujo muestra la escena completa; sus 6 pasos aparecen abajo.
Todos los pasos en texto
- Dos literales. Escribes 0.1 + 0.2. Python lee cada literal como texto decimal y tiene que guardarlo como una fracción binaria con 53 bits significativos.
- Cada literal se redondea. 0.1 es 1/10, y 10 tiene un factor 5, así que sus cifras binarias nunca terminan. Python conserva el valor de 53 bits más cercano, un poco más que 0.1, y 0.2 también se guarda un poco alto.
- La suma exacta. La suma calcula primero la suma exacta de los dos valores guardados. Cae exactamente a medio camino entre dos floats vecinos, y entre esos dos no hay ningún float.
- Un empate va al par. El resultado tiene que ser uno de los dos floats, así que el empate se resuelve con la regla de IEEE 754, hacia el float cuya última cifra binaria es par. Aquí es el de arriba.
- El decimal más corto. Para imprimir un float, Python elige el decimal más corto que se guardaría como el mismo float. Para esta suma hacen falta 17 cifras, 0.30000000000000004.
- Por qué == dice False. El literal 0.3 solo pasa por las dos primeras etapas y se guarda como el float justo por debajo de la suma. Dos floats distintos no son iguales, así que 0.1 + 0.2 == 0.3 es False.
Punto de control
Comprueba: lo que guarda un float
El rango de un float es enorme, pero su precisión no
import sys
print(sys.float_info.max) # the largest float
print(sys.float_info.max * 10) # past it: infinity, and no error
try:
sys.float_info.max ** 2 # ** checks for overflow
except OverflowError:
print("** raised OverflowError")
print(sys.float_info.epsilon) # the gap between 1.0 and the next float up
print(2.0 ** 53 + 1 == 2.0 ** 53) # above 2 ** 53 not every whole number fits
print(float(2 ** 53 + 1)) Salida
1.7976931348623157e+308 inf ** raised OverflowError 2.220446049250313e-16 True 9007199254740992.0
Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 float_limits.py
Se ejecuta en este dispositivo, en tu navegador. La primera ejecución descarga Python (unos 13.5 MB), que se guarda para las siguientes.
Tu ejecución, en este navegador
- El float más grande es de unos 1.8 × 10³⁰⁸. Más allá, una multiplicación da
inf(infinito) sin ningún error, mientras que**lanzaOverflowError(las líneastryyexceptlo capturan, como en la lección de los enteros): Python solo comprueba el desbordamiento en algunas operaciones con floats. sys.float_info.epsilon, el épsilon de máquina: La distancia entre 1.0 y el siguiente float hacia arriba, unos 2.2 × 10⁻¹⁶ para un float de Python (sys.float_info.epsilon). Es la razón por la que los floats guardan unas 16 cifras significativas., unos 2.2 × 10⁻¹⁶, es la distancia entre 1.0 y el siguiente float. Cada operación puede desviarse más o menos la mitad de eso en relación con su resultado, y por eso los floats sirven para unas 16 cifras significativas.- Los números enteros solo son exactos hasta 2⁵³ (9,007,199,254,740,992). Por encima, las distancias entre floats son mayores que 1, así que
float(2 ** 53 + 1)vuelve como 2⁵³. Los int de Python no tienen ese límite, así que guarda los recuentos y el dinero en int cuando puedas.
Pulsa Editar, cambia los dos + 1 de las dos últimas líneas por + 2 y vuelve a ejecutar el programa. 2⁵³ + 2 es en sí un float, el siguiente hacia arriba, así que la comparación imprime False y la última línea imprime 9007199254740994.0.
Punto de control
Comprueba: el rango y la precisión de un float
Los floats calculados necesitan una tolerancia, así que usa math.isclose()
Como un float calculado suele llevar un error de redondeo diminuto, == es la prueba equivocada para él. Pregunta en su lugar si dos valores están lo bastante cerca:
import math
total = 0.1 + 0.2
print(total == 0.3)
print(math.isclose(total, 0.3)) # within a relative tolerance of 1e-09
balance = 100.0 - 99.9 - 0.1 # should be zero
print(balance)
print(math.isclose(balance, 0.0)) # relative tolerance alone never accepts 0
print(math.isclose(balance, 0.0, abs_tol=1e-9)) # add an absolute tolerance Salida
False True -5.689893001203927e-15 False True
Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 compare_floats.py
Se ejecuta en este dispositivo, en tu navegador. La primera ejecución descarga Python (unos 13.5 MB), que se guarda para las siguientes.
Tu ejecución, en este navegador
math.isclose(a, b) acepta los dos valores cuando su diferencia es como mucho rel_tol veces el mayor de los dos en magnitud, una tolerancia relativa: La mayor diferencia permitida entre dos valores, como proporción del mayor. math.isclose() usa rel_tol=1e-09 por defecto, unas nueve cifras coincidentes.; el rel_tol=1e-09 por defecto significa unas nueve cifras coincidentes, sea cual sea el tamaño de los números. Cerca de cero esa regla falla: ningún número salvo el 0 está cerca de 0 en relación con su propio tamaño, así que el balance, que debería ser cero pero es -5.7 × 10⁻¹⁵, se rechaza. Añade una tolerancia absoluta: Una diferencia máxima fija permitida entre dos valores, sea cual sea su tamaño. math.isclose() necesita una, abs_tol, cuando un valor puede ser cero o estar cerca., abs_tol, siempre que un valor pueda ser cero o casi, y elígela según el problema: para una cantidad en rupias o en dólares, como este saldo, 1e-9 es una diezmillonésima de paisa o de centavo, muy por debajo de nada que pueda contener una cuenta.
math.isclose() sigue las reglas de IEEE 754 para los valores especiales: el infinito solo está cerca de sí mismo, y NaN: Not a Number (no es un número), el resultado float de una operación sin respuesta razonable, como infinito menos infinito. Toda comparación con él es False, incluso nan == nan; detéctalo con math.isnan(). (abajo) no está cerca de nada, ni siquiera de otro NaN.
Idea clave
Prueba un float calculado con math.isclose(), nunca con ==, y dale un abs_tol siempre que el valor con el que comparas pueda ser cero o estar cerca.
Punto de control
Comprueba: comparar con una tolerancia
Los errores de redondeo se acumulan cuando sumas muchos floats
Cada suma redondea su resultado, y los pequeños errores se pueden acumular. Recorre diez sumas de 0.1 y mira cómo total se aleja de los valores decimales y vuelve:
Programa · adding_up.py
import math
total = 0.0
for _ in range(10):
total += 0.1 # ten additions, each one rounded
print(total)
print(sum([0.1] * 10)) # sum() keeps track of the rounding errors
print(math.fsum([0.1] * 10))
values = [1e16, 1e-16, 1.0]
print(sum(values), math.fsum(values)) # here only fsum() gets it right Variables
- recién tomado por el bucle
- cambiado en este paso
- ya recorrido por el bucle
- se lee a continuación
- donde se detuvo el bucle
- el valor stop, que nunca se da
Impreso hasta ahora
Todavía no hay nada impreso0.9999999999999999 1.0 1.0 1e+16 1.0000000000000002e+16
Cada paso se registró de una ejecución con Python 3.14.8 en macOS 26 arm64.
El dibujo muestra las variables cuando termina el programa, y los 29 pasos del programa aparecen abajo.
Cambia el programa y ejecútalo aquí. Se ejecuta en este dispositivo, en tu navegador. La primera ejecución descarga Python (unos 13.5 MB), que se guarda para las siguientes.
Tu ejecución, en este navegador
Todos los pasos en texto
- Inicio. Todavía no se ha ejecutado ninguna línea, así que no hay variables. Python empieza por la línea 1.
- Línea 1.
import mathcargamath. - Línea 3.
total = 0.0asigna0.0atotal. - Línea 4.
for _ in range(10):da su primer número:_es0. - Línea 5.
total += 0.1cambiatotalde0.0a0.1. - Línea 4.
for _ in range(10):da el número siguiente:_es1. - Línea 5.
total += 0.1cambiatotalde0.1a0.2. - Línea 4.
for _ in range(10):da el número siguiente:_es2. - Línea 5.
total += 0.1cambiatotalde0.2a0.30000000000000004. Lo clave La tercera suma cae en el float justo por encima del más cercano a 0.3, el 0.30000000000000004 de la primera línea de la lección. - Línea 4.
for _ in range(10):da el número siguiente:_es3. - Línea 5.
total += 0.1cambiatotalde0.30000000000000004a0.4. El redondeo puede anular un error igual que crearlo. Esta suma cae en el float más cercano a 0.4, que se imprime como 0.4. - Línea 4.
for _ in range(10):da el número siguiente:_es4. - Línea 5.
total += 0.1cambiatotalde0.4a0.5. - Línea 4.
for _ in range(10):da el número siguiente:_es5. - Línea 5.
total += 0.1cambiatotalde0.5a0.6. - Línea 4.
for _ in range(10):da el número siguiente:_es6. - Línea 5.
total += 0.1cambiatotalde0.6a0.7. - Línea 4.
for _ in range(10):da el número siguiente:_es7. - Línea 5.
total += 0.1cambiatotalde0.7a0.7999999999999999. De aquí en adelante el total está un float por debajo del más cercano a cada valor decimal, y las dos sumas siguientes lo mantienen ahí. - Línea 4.
for _ in range(10):da el número siguiente:_es8. - Línea 5.
total += 0.1cambiatotalde0.7999999999999999a0.8999999999999999. - Línea 4.
for _ in range(10):da el número siguiente:_es9. - Línea 5.
total += 0.1cambiatotalde0.8999999999999999a0.9999999999999999. - Línea 4. A
for _ in range(10):no le quedan números: un range se detiene antes de su valor stop,10, así que el bucle termina. - Línea 6.
print(total)imprime0.9999999999999999. Diez sumas hicieron diez redondeos, y el total termina un float por debajo de 1.0. - Línea 8.
print(sum([0.1] * 10))imprime1.0.sum()suma los mismos diez valores pero lleva la cuenta de los errores de redondeo sobre la marcha, así que termina en 1.0. - Línea 9.
print(math.fsum([0.1] * 10))imprime1.0. - Línea 11.
values = [1e16, 1e-16, 1.0]hace devaluesuna lista de 3 elementos. - Línea 12.
print(sum(values), math.fsum(values))imprime1e+16 1.0000000000000002e+16. Era la última línea, así que el programa termina.
El bucle redondea después de cada paso y termina un poco por debajo de 1.0. sum() lleva la cuenta de los errores de redondeo sobre la marcha (un algoritmo llamado suma de Neumaier) y obtiene exactamente 1.0. math.fsum() va más allá y sigue varias sumas parciales, así que no se pierde precisión por el camino. La última línea muestra un caso en el que solo fsum() acierta: el total real es un poco más de 10000000000000001, y el float más cercano a eso es 1.0000000000000002e+16.
Nota de versión
El sum() más preciso de los floats es nuevo en Python 3.12. Python 3.11 y anteriores suman los floats uno a uno, así que allí sum([0.1] * 10) da 0.9999999999999999, lo mismo que el bucle.
Punto de control
Comprueba: sumar muchos floats
round() redondea el valor guardado, no el que escribiste
from decimal import Decimal
print(round(2.675, 2)) # 2.67, not 2.68
print(Decimal(2.675)) # the value really stored is a little below 2.675
print(f"{2.675:.2f}") # formatting rounds the stored value too
print(round(0.125, 2)) # 0.125 is exact: a true tie, so half to even
percent = 0.57 * 100
print(percent)
print(int(percent)) # int() drops the fraction: 56
print(round(percent)) # round first: 57 Salida
2.67 2.67499999999999982236431605997495353221893310546875 2.67 0.12 56.99999999999999 56 57
Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 rounding_surprises.py
Se ejecuta en este dispositivo, en tu navegador. La primera ejecución descarga Python (unos 13.5 MB), que se guarda para las siguientes.
Tu ejecución, en este navegador
round(2.675, 2) da 2.67, no 2.68, porque el float escrito como 2.675 en realidad está un poco por debajo, así que no hay empate que redondear hacia arriba. Formatear con :.2f redondea el mismo valor guardado y coincide. 0.125, en cambio, es exactamente ⅛, un empate de verdad, y round() lo manda al vecino par, 0.12.
Un error común: int() después de una aritmética con floats
0.57 * 100 es 56.99999999999999, y int() quita la fracción, así que el resultado es 56 en lugar de 57. Los porcentajes, los precios en paise o en centavos y otros pasos de “convertir a un número entero” fallan así. Usa round() en su lugar, que también devuelve un int.
Punto de control
Comprueba: redondear el valor guardado
El infinito, NaN y el cero negativo siguen sus propias reglas
import math
inf = float("inf")
nan = float("nan") # "not a number"
print(inf > 10 ** 300, -inf < 0)
print(inf - inf, inf * 0) # no sensible answer: nan
print(nan == nan, nan != nan) # nan is not equal even to itself
print(math.isnan(nan), math.isinf(inf), math.isfinite(1e308))
print(0.0 == -0.0, math.copysign(1, -0.0)) # equal, but the sign is kept Salida
True True nan nan False True True True True True -1.0
Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 special_values.py
Se ejecuta en este dispositivo, en tu navegador. La primera ejecución descarga Python (unos 13.5 MB), que se guarda para las siguientes.
Tu ejecución, en este navegador
float("inf")y-float("inf")son mayor y menor que cualquier número finito.nan(“not a number”, no es un número) es el resultado de las operaciones sin respuesta razonable, como infinito menos infinito. Toda comparación con él esFalse, inclusonan == nan; solo!=esTrue. Detéctalo conmath.isnan(), y el infinito conmath.isinf(), o “un número normal” conmath.isfinite().-0.0es igual a0.0, pero conserva su signo, quemath.copysign()puede leer.
Un NaN en tus datos es fácil de pasar por alto, porque las comparaciones con él fallan en silencio. max() se queda con el primer elemento y solo lo sustituye por uno que compare como mayor, así que un NaN al principio gana y un NaN más adelante se ignora. Y dividir un float entre cero no da inf en Python; lanza ZeroDivisionError:
nan = float("nan")
print(max([nan, 3.0, 1.0])) # nan: no reading compares greater than nan
print(max([1.0, 3.0, nan])) # 3.0: the same readings in another order
try:
1.0 / 0.0
except ZeroDivisionError as error: # Python raises instead of giving inf
print("ZeroDivisionError:", error) Salida
nan 3.0 ZeroDivisionError: division by zero
Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 nan_in_data.py
Se ejecuta en este dispositivo, en tu navegador. La primera ejecución descarga Python (unos 13.5 MB), que se guarda para las siguientes.
Tu ejecución, en este navegador
Comprueba los valores que llegan con math.isnan(), y deja fuera NaN o sustitúyelo antes de calcular un máximo, una ordenación o un total.
Punto de control
Comprueba: infinito, NaN y cero negativo
Decimal y Fraction son exactos donde un float no lo es
La biblioteca estándar tiene otros dos tipos de número para los trabajos en los que el redondeo binario no es aceptable. Una lección posterior trata los dos a fondo; esto es cuándo recurrir a cada uno:
from decimal import Decimal
from fractions import Fraction
print(Decimal("0.1") + Decimal("0.2")) # decimal arithmetic, from strings
print(Decimal("0.1") + Decimal("0.2") == Decimal("0.3"))
print(Decimal(0.1)) # from a float: the float's error comes along
print(Fraction(1, 3) + Fraction(1, 6)) # exact ratios
print(Fraction(1, 3) * 3 == 1)
print(float.from_number(Fraction(1, 4))) # new in 3.14: numbers only
try:
float.from_number("3.5") # float("3.5") would parse the text
except TypeError as error:
print("TypeError:", error) Salida
0.3 True 0.1000000000000000055511151231257827021181583404541015625 1/2 True 0.25 TypeError: must be real number, not str
Salida registrada con Python 3.14.8 en macOS 26 arm64. Para ejecutarlo tú: mise exec python@3.14.8 -- python3 exact_types.py
Se ejecuta en este dispositivo, en tu navegador. La primera ejecución descarga Python (unos 13.5 MB), que se guarda para las siguientes.
Tu ejecución, en este navegador
| Criterio | float | decimal.Decimal | fractions.Fraction |
|---|---|---|---|
| Guarda | una fracción binaria con 53 bits significativos | cifras decimales: 28 cifras significativas por defecto, ajustable | un numerador y un denominador exactos |
| Exacto para | mitades, cuartos, octavos… y números enteros hasta 2⁵³ | cantidades decimales como 0.1 y 19.99 | cualquier cociente de dos enteros, como 1/3 |
| Aritmética | el hardware de punto flotante del procesador | software | software, con numeradores y denominadores que pueden crecer mucho |
| Cuándo elegirlo | Medidas, ciencia, gráficos y la mayoría de los cálculos del día a día | Dinero, y todo lo que tenga que redondear como la gente redondea los decimales | Cocientes exactos, como probabilidades o escalar recetas |
Construye un Decimal a partir de una cadena, Decimal("0.1"), o de un int. Decimal(0.1) copia el float exactamente, con error y todo, como muestra la tercera línea de la salida.
Nota de versión
Novedad de Python 3.14: float.from_number() convierte un número (un int, un Fraction, un Decimal…) en un float y lanza TypeError con cualquier otra cosa, incluido el texto. float() sigue analizando cadenas como "3.5", así que from_number() es la opción más estricta cuando un valor ya tiene que ser un número.
Punto de control
Comprueba: Decimal y Fraction
Practica: compara floats de la forma segura
Dos funciones pequeñas ponen a trabajar las reglas de comparación de esta lección: una decide si dos floats son iguales salvo errores de redondeo, y la otra encuentra los pares en los que == se equivoca. Sus pruebas de ejemplo se ejecutan en tu navegador, y la primera pista está ahí cuando la necesites:
Ejercicio · Fácil · Python
Comparar floats con una tolerancia
Escribe dos funciones en compare.py.
La primera, almost_equal(a, b), devuelve True cuando los floats a y b son iguales salvo errores de redondeo, y False en caso contrario. Usa math.isclose() con su tolerancia relativa por defecto (rel_tol=1e-09, unas nueve cifras coincidentes) y una tolerancia absoluta de ABS_TOL, que el código inicial fija en 1e-12, para que los resultados cerca de cero también cuenten como cercanos.
La segunda, surprises(pairs), recibe una lista de pares (a, b) y devuelve, en su orden original, los pares que == considera distintos pero almost_equal() considera iguales: las comparaciones en las que == se equivocaría.
almost_equal(0.1 + 0.2, 0.3)esTruealmost_equal(1e-15, 0.0)esTruealmost_equal(1e-9, 0.0)esFalsealmost_equal(float("nan"), float("nan"))esFalsesurprises([(0.1 + 0.2, 0.3), (1.0, 1.0), (2.0, 3.0)])es[(0.30000000000000004, 0.3)]
Las pruebas de ejemplo comparan doce pares, entre ellos infinitos, NaN, 0.0 y -0.0, y valores muy cerca de cero.
Código inicial · compare.py
import math
ABS_TOL = 1e-12 # how far apart two numbers near zero may be and still count as equal
def almost_equal(a, b):
"""Return True when a and b are equal within rel_tol=1e-09 or abs_tol=ABS_TOL."""
# Replace this line with your code.
return a == b
def surprises(pairs):
"""Return the pairs (a, b) that == calls different but almost_equal() calls equal, in order."""
# Replace this line with your code.
return [] Las pruebas de ejemplo · test_compare.py
from compare import almost_equal, surprises
nan = float("nan")
inf = float("inf")
def test_rounding_errors():
"""treats numbers as equal when they differ only by a tiny fraction of their size"""
assert almost_equal(0.1 + 0.2, 0.3) is True
assert almost_equal(1.1 + 2.2, 3.3) is True
assert almost_equal(1e20, 1e20 + 1e5) is True
def test_real_differences():
"""keeps numbers that really differ apart"""
assert almost_equal(1.0, 1.001) is False
assert almost_equal(100.0, 100.5) is False
def test_relative_tolerance():
"""allows a relative difference of 1e-09, and no more"""
assert almost_equal(1.0, 1.0 + 5e-10) is True
assert almost_equal(1.0, 1.0 + 2e-9) is False
def test_near_zero():
"""allows an absolute difference of 1e-12 near zero, and no more"""
assert almost_equal(1e-15, 0.0) is True
assert almost_equal(100.0 - 99.9 - 0.1, 0.0) is True
assert almost_equal(2e-12, 0.0) is False
assert almost_equal(1e-9, 0.0) is False
def test_signed_zero():
"""treats 0.0 and -0.0 as equal"""
assert almost_equal(0.0, -0.0) is True
def test_special_values():
"""follows the IEEE 754 rules for infinity and NaN"""
assert almost_equal(inf, inf) is True
assert almost_equal(inf, -inf) is False
assert almost_equal(nan, nan) is False
assert almost_equal(nan, 1.0) is False
def test_surprises():
"""finds the pairs that == gets wrong, in their original order"""
pairs = [
(0.1 + 0.2, 0.3),
(1.0, 1.0),
(1e-15, 0.0),
(1e-9, 0.0),
(0.0, -0.0),
(nan, nan),
(inf, inf),
(inf, -inf),
(1e20, 1e20 + 1e5),
(1.0, 1.001),
(1.1 + 2.2, 3.3),
(2.5, 2.5),
]
assert surprises(pairs) == [(0.1 + 0.2, 0.3), (1e-15, 0.0), (1e20, 1e20 + 1e5), (1.1 + 2.2, 3.3)] Una pista
math.isclose(a, b, abs_tol=ABS_TOL) ya usa rel_tol=1e-09, su valor por defecto. Para surprises(), empieza con una lista vacía, recorre los pares con for a, b in pairs:, y append((a, b)) cuando a != b y almost_equal(a, b) sean verdaderas las dos.
Resultados de las pruebas de ejemplo
| Prueba | Resultado | Detalles |
|---|
Lo que imprimió tu código
Las pruebas de ejemplo se ejecutan en este dispositivo, en tu navegador (Pyodide): no se envía nada a mysmartcopilot.com. La primera ejecución descarga Python (unos 13.5 MB), que se guarda para las siguientes. Una comprobación en tu navegador es una orientación para ti, no la prueba de que el código sea correcto con cualquier entrada.
Cuando pasen las pruebas, intenta las preguntas de abajo. Empiezan donde tus respuestas son más flojas, suben de nivel a medida que aciertas y explican cada opción en cuanto la eliges:
Práctica que se adapta a ti
Practica esta lección
18 preguntas sobre 7 ideas de esta lección. Cada respuesta se explica al momento, y lo que aciertas se guarda en este navegador.
Preguntas de entrevista
Júnior · ¿Por qué 0.1 + 0.2 no es igual a 0.3 en Python?
Porque ninguno de los tres se puede guardar exacto. Un float es un doble IEEE 754, una fracción binaria con 53 bits significativos, y el binario solo guarda exacta una fracción si su denominador es una potencia de 2. El denominador de 1/10 no lo es, así que el literal 0.1 se guarda como el float más cercano, un poco por encima de 0.1, y 0.2 un poco por encima de 0.2. Su suma exacta queda a medio camino entre dos floats vecinos y se redondea al par, el float que se imprime como 0.30000000000000004, mientras que el literal 0.3 se guarda como el float justo por debajo. Dos floats distintos no son iguales. No hay nada roto, y todos los lenguajes que usan estos floats obtienen la misma respuesta. Compara los floats calculados con math.isclose(), y usa decimal.Decimal, construido a partir de cadenas, cuando cantidades decimales como el dinero tengan que ser exactas.
Para recordar
- Los floats son fracciones binarias con 53 bits significativos; la mayoría de las fracciones decimales, 0.1 incluida, se guardan como el valor de ese tipo más cercano, así que los pequeños errores son normales.
- Nunca compares floats calculados con
==. Usamath.isclose(), con unabs_tolsiempre que los valores puedan estar cerca de cero. sum()suma floats con precisión desde Python 3.12;math.fsum()es el más preciso.round(2.675, 2)es 2.67 por el valor guardado; usaround(), noint(), cuando conviertas un resultado float en un número entero.- NaN no es igual a sí mismo: detéctalo con
math.isnan(). - Usa
Decimal, construido a partir de cadenas, para el dinero, yFractionpara los cocientes exactos; deja los floats para las medidas y la ciencia.
Términos de esta lección
- tolerancia absoluta
- Una diferencia máxima fija permitida entre dos valores, sea cual sea su tamaño. math.isclose() necesita una, abs_tol, cuando un valor puede ser cero o estar cerca.
- IEEE 754
- El estándar de aritmética de punto flotante que siguen casi todas las computadoras. Un float de Python es su doble de 64 bits, una fracción binaria con 53 bits significativos y un exponente.
- épsilon de máquina
- La distancia entre 1.0 y el siguiente float hacia arriba, unos 2.2 × 10⁻¹⁶ para un float de Python (sys.float_info.epsilon). Es la razón por la que los floats guardan unas 16 cifras significativas.
- NaN
- Not a Number (no es un número), el resultado float de una operación sin respuesta razonable, como infinito menos infinito. Toda comparación con él es False, incluso nan == nan; detéctalo con math.isnan().
- tolerancia relativa
- La mayor diferencia permitida entre dos valores, como proporción del mayor. math.isclose() usa rel_tol=1e-09 por defecto, unas nueve cifras coincidentes.
Referencias
- El tutorial de Python: aritmética de punto flotante, problemas y limitaciones (en inglés) (Python Software Foundation)
- math.isclose() (en inglés) (Python Software Foundation)
- math.fsum() (en inglés) (Python Software Foundation)
- Funciones integradas: sum() (en inglés) (Python Software Foundation)
- Funciones integradas, round() (en inglés) (Python Software Foundation)
- sys.float_info (en inglés) (Python Software Foundation)
- Comparaciones de valores (valores no numéricos) (en inglés) (Python Software Foundation)
- decimal, aritmética decimal de punto fijo y de punto flotante (en inglés) (Python Software Foundation)
- fractions, números racionales (en inglés) (Python Software Foundation)
- float.from_number() (en inglés) (Python Software Foundation)
- Novedades de Python 3.12: otros cambios del lenguaje (en inglés) (Python Software Foundation)
- Novedades de Python 3.14: funciones integradas (en inglés) (Python Software Foundation)
- Excepciones integradas: OverflowError (en inglés) (Python Software Foundation)
- Estándar IEEE de aritmética de punto flotante (IEEE 754) (en inglés) (IEEE)
Herramientas relacionadas
Informar de un problema con esta lección (en inglés)
Solo se guarda en este navegador. Tu progreso en Learn (en inglés)