Desbordamiento de enteros y limitaciones de punto flotante
Al finalizar este tema
Comprenderá por qué ocurre el desbordamiento de enteros, podrá explicar las limitaciones de precisión del punto flotante y conocerá las precauciones necesarias al realizar comparaciones numéricas.
Las computadoras cometen errores con los números
print(0.1 + 0.2) # 0.30000000000000004print(0.1 + 0.2 == 0.3) # False!Esto no es un error de Python. Ocurre en C, Java, JavaScript, Rust y en todos los demás lenguajes. Se debe a la forma en que las computadoras almacenan los números de punto flotante.
Desbordamiento de enteros
Número de bits y rango
En las computadoras, los enteros se representan con un número fijo de bits.
Entero de 8 bits sin signo: 0 ~ 255
Entero de 8 bits con signo: -128 ~ 127
Entero de 32 bits con signo: -2.147.483.648 ~ 2.147.483.647
Entero de 64 bits con signo: -9,2 × 10^18 ~ 9,2 × 10^18Al exceder el rango, se produce un desbordamiento.
Qué es el desbordamiento
En un entero sin signo de 8 bits, ¿cuánto es 255 + 1?
11111111 (255)
+ 00000001 (1)
----------
100000000 (256, pero se descarta el noveno bit)
= 00000000 (0)¡Después de 255, vuelve a 0! Esto es un desbordamiento. Es como el cuentakilómetros de un coche que pasa de 999.999 a 000.000.
Casos reales de errores
// C: desbordamiento de un entero de 32 bits
int balance = 2147483647; // Valor máximo
balance = balance + 1; // -2147483648: pasa de positivo a negativo- Explosión del cohete Ariane 5 (1996): Conversión de un valor de 64 bits a 16 bits → desbordamiento (overflow) → autodestrucción del cohete
- Visualizaciones de YouTube de "Gangnam Style" (2014): Superación del límite de enteros de 32 bits (2.100 millones) → Google cambió a 64 bits
Python no tiene desbordamiento
# Python no limita el tamaño de los enterosbig = 2 ** 100print(big) # 1267650600228229401496703205376
bigger = 2 ** 1000print(len(str(bigger))) # Un número de 302 dígitosPython expande la memoria automáticamente según sea necesario. Sin embargo, en la mayoría de los lenguajes, como C, Java y JavaScript, el tamaño de los enteros es fijo, por lo que se debe tener cuidado con el desbordamiento (overflow).
NumPy también puede presentar desbordamientos, ya que los arreglos de NumPy utilizan enteros de tamaño fijo al estilo de C:
import numpy as np
a = np.int32(2147483647)print(a + 1) # -2147483648 (desbordamiento)
b = np.int64(2147483647)print(b + 1) # 2147483648 (seguro con 64 bits)Punto flotante — IEEE 754
Al convertir 0,1 a binario
Así como en el sistema decimal 1/3 = 0,333333... es un número decimal periódico, en el sistema binario 0,1 = 0,0001100110011... es un número binario periódico.
0,1 (decimal) = 0,00011001100110011001100110011... (binario, repetición infinita)La computadora almacena este número decimal infinito utilizando un número finito de bits (64 bits), por lo que debe truncarlo en algún punto. Este truncamiento es la causa del error.
IEEE 754 — Punto flotante de 64 bits
[1 bit de signo][11 bits de exponente][52 bits de mantisa]
0 01111111011 1001100110011001100110011001100110011001100110011010
Signo: 0 (positivo)
Exponente: determina el exponente real
Mantisa: cifras significativas (52 bits ≈ 15~17 dígitos de precisión)# Precisión limitada a 15~17 dígitosprint(f"{0.1:.20f}") # 0.10000000000000000555print(f"{0.2:.20f}") # 0.20000000000000001110print(f"{0.3:.20f}") # 0.29999999999999998890Dado que ni 0.1 ni 0.2 son precisos, el resultado de la suma tampoco lo es.
Problemas prácticos y soluciones
1. Comparación: no comparar con el == absoluto
# Badif 0.1 + 0.2 == 0.3: print("Equal") # No se ejecuta
# Bien: usa una tolerancia epsilonepsilon = 1e-9if abs((0.1 + 0.2) - 0.3) < epsilon: print("Equal") # Se ejecuta
# Mejor: usa math.iscloseimport mathif math.isclose(0.1 + 0.2, 0.3): print("Equal") # Se ejecuta2. Error acumulado: aumenta en los cálculos iterativos
total = 0.0for _ in range(1000): total += 0.1print(total) # 99.99999999999857 (no es 100)print(f"Error: {abs(total - 100):.15f}") # 0.0000000000014323. Cálculos financieros — Uso de Decimal
from decimal import Decimal
# float: se produce un error de precisiónprice = 0.1 + 0.2print(price) # 0.30000000000000004
# Decimal: exactoprice = Decimal('0.1') + Decimal('0.2')print(price) # 0.3 (exacto)
# Atención: debe inicializarse con una cadenaDecimal(0.1) # Decimal('0.1000000000000000055511151231257827021181583404541015625')Decimal('0.1') # Decimal('0.1'): exactoMontos, impuestos, tasas de interés: en cálculos donde la precisión es crucial, se utiliza Decimal en lugar de float.
Números en JavaScript: enteros y punto flotante
JavaScript no tiene un tipo de dato entero. Todos los números son de punto flotante de 64 bits (IEEE 754).
// JavaScript: pérdida de precisión con enteros grandes
console.log(9007199254740992 === 9007199254740993); // true!
// Considera iguales ambos números porque superan la mantisa de 52 bits
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 (2^53 - 1)Por esta razón, se introdujo BigInt en JavaScript:
const big = 9007199254740993n; // Literal BigInt con sufijo n
console.log(big === 9007199254740993n); // true: exactoEsta es también la razón por la que los IDs de los tuits de Twitter (actualmente X) se transmiten como cadenas de texto: si se analizan como números en JSON, se pierde la precisión, por lo que la API también proporciona el ID como una cadena "id_str".
Valores especiales
# Infinitoprint(float('inf')) # infprint(float('inf') + 1) # infprint(float('inf') * -1) # -inf
# NaN (Not a Number)print(float('nan')) # nanprint(float('nan') == float('nan')) # False: NaN ni siquiera es igual a sí mismo
import mathprint(math.isnan(float('nan'))) # True: forma correcta de comprobarloprint(math.isinf(float('inf'))) # TrueNaN es el único valor que no es igual a sí mismo y cuyo == es False. Para verificar NaN, se debe usar siempre math.isnan().
Resumen clave
| Concepto | Resumen |
|---|---|
| Desbordamiento de enteros | El valor se invierte al superar el número fijo de bits. Python es una excepción (expansión automática) |
| IEEE 754 | Estándar de punto flotante de 64 bits. 52 bits de mantisa ≈ precisión de 15 a 17 dígitos |
| 0.1 + 0.2 ≠ 0.3 | 0.1 es un decimal periódico en binario → se trunca al almacenar → genera error |
| Comparación | Usar math.isclose() en lugar de == |
| Cálculos financieros | Usar Decimal en lugar de float |
| NaN | Un valor que no es igual a sí mismo. Verificar con math.isnan() |
La idea de que "las computadoras realizan cálculos con precisión exacta" es un error. Dado que las computadoras representan números infinitos con un número finito de bits, tienen límites inherentes de precisión. Comprender estos límites aclara por qué se usa Decimal en sistemas financieros, por qué las coordenadas en los juegos "tiemblan" y por qué es necesario el análisis de errores en cálculos científicos.
Reglas prácticas: Los enteros son seguros en Python; en otros lenguajes, verificar el rango. Para decimales, está estrictamente prohibido usar ==; para finanzas, usar Decimal. Verificar NaN con math.isnan().