Errores de punto flotante y numéricos
Los flotantes binarios no pueden representar muchas fracciones decimales de forma exacta. Los totales financieros, los umbrales y las comprobaciones de igualdad fallan cuando el código trata float como aritmética decimal exacta.
Receta
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
from decimal import Decimal, ROUND_HALF_UP
price = Decimal("19.99")
qty = Decimal("3")
total = (price * qty).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
# Nunca: if a == b para flotantes
import math
if math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0):
...Cuándo recurrir a esto:
0.1 + 0.2 != 0.3rompe pruebas o facturación- Las agregaciones
float64de Pandas no coinciden con los totales de las hojas de cálculo - La ordenación o la agrupación producen un error de "uno de más" en los límites
- Las API JSON devuelven colas flotantes ruidosas (
19.990000000002)
Ejemplo de trabajo
from decimal import Decimal, ROUND_HALF_UP, getcontext
import math
# --- sorpresa clásica de flotantes ---
assert 0.1 + 0.2 != 0.3 # True en flotante IEEE 754
# --- dinero con Decimal ---
def line_total(unit_price: str, qty: int) -> Decimal:
price = Decimal(unit_price)
return (price * qty).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
assert line_total("19.99", 3) == Decimal("59.97")
# --- comparación segura de flotantes ---
def nearly_equal(a: float, b: float) -> bool:
return math.isclose(a, b, rel_tol=1e-9, abs_tol=1e-12)
# --- agregación consciente de pandas 2.2+ ---
import pandas as pd
df = pd.DataFrame({"amount": ["10.10", "20.20"]})
# Ruta de BUG: astype(float) temprano aún puede acumular errores binarios a escala
df["amount_dec"] = df["amount"].map(lambda x: Decimal(x))
total = sum(df["amount_dec"], Decimal("0"))
assert total == Decimal("30.30")
# --- contexto para reglas bancarias ---
getcontext().prec = 28
tax_rate = Decimal("0.0825")
subtotal = Decimal("100.00")
tax = (subtotal * tax_rate).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
assert tax == Decimal("8.25")Lo que esto demuestra:
- La igualdad de flotantes no es segura;
math.isclosecompara con tolerancia Decimalconstruido a partir de cadenas preserva la intención decimalquantizeaplica reglas de redondeo explícitas para el dinero- Las canalizaciones de pandas deben retrasar la conversión a flotante o permanecer en Decimal hasta la presentación
Inmersión profunda
Cómo funciona
floates IEEE 754 binary64; muchos racionales decimales no tienen representación binaria exactaDecimalalmacena coeficientes base-10 con precisión y modos de redondeo ajustablesFractionmaneja números racionales exactamente cuando los valores son racionales- Los céntimos enteros (
int) evitan por completo los flotantes para el dinero cuando la escala es fija
Elección de tipo
| Caso de uso | Tipo | Por qué |
|---|---|---|
| Dinero, impuestos, facturas | Decimal o céntimos enteros | reglas decimales deterministas |
| Ciencia/ingeniería | float + isclose | rendimiento, ecosistema numpy |
| Ratios como 1/3 | Fraction | racional exacto |
| Características de ML | float32/float64 | las API de tensores esperan flotantes |
Notas de Python
# Decimal MALO a partir de flotante - hereda el error binario
Decimal(0.1) # Decimal('0.1000000000000000055511151231257827021181583404541015625')
# BUENO
Decimal("0.1")Trampas
- Constructor
Decimal(float)- importa la imprecisión del flotante a Decimal. Solución:Decimal(str(x))o analizar a partir de la entrada de cadena. - Mezclar Decimal y float en operaciones - genera
TypeErroro se codifica de forma inesperada. Solución: elegir un tipo por límite. - Serialización JSON -
Decimalno es nativo de JSON. Solución: cuantificar a cadena o céntimos enteros en la capa de API. - Orden de suma de float64 de numpy/pandas - las sumas no asociativas difieren por orden. Solución: suma de Kahan, pasada de Decimal o céntimos enteros.
floor/ceilen negativos -int(-1.2)trunca hacia cero, no hacia abajo. Solución:math.floor/math.ceil.- Redondeo de visualización frente a valor almacenado - la interfaz de usuario muestra 2 decimales, el backend compara el flotante completo. Solución: cuantificar en el límite de comparación.
Alternativas
| Alternativa | Usar cuándo | No usar cuándo |
|---|---|---|
| Unidades menores enteras (céntimos) | escala de moneda fija | FX con muchos lugares decimales |
Decimal | reglas decimales humanas | bucles internos de ML de alto rendimiento |
float + épsilon | gráficos/simulación | totales financieros regulatorios |
| Tipo de datos Decimal de Polars 1.x | columnas decimales ETL tipadas | scripts simples |
Preguntas frecuentes
¿Por qué Python muestra 0.30000000000000004?
Esa es la representación binaria64 más cercana. print muestra la forma de ronda más corta; el valor no es exactamente 0.3.
¿Deberían las API usar flotante o cadena para el dinero?
Prefiera decimal de cadena o unidades menores enteras en JSON. Si el flotante es inevitable, documente la tolerancia y nunca use la igualdad bruta.
¿Qué modo de redondeo debe usar la facturación?
Depende de la regulación; ROUND_HALF_UP es común para minoristas. Codifique la regla en una función y pruebe casos extremos (0.5 ulp).
¿Es math.isclose suficiente para el dinero?
No para la igualdad del dinero. Use Decimal quantize o céntimos enteros. isclose se adapta a flotantes científicos.
¿Cómo ayudan las columnas nullable Int64 de pandas?
Almacene céntimos como Int64 en pandas 2.2+ para evitar el flotante por completo hasta el formato de visualización.
¿Polars maneja Decimal?
Polars 1.x admite tipos de datos Decimal para ETL; convierta a cadena/entero para API JSON explícitamente.
¿Qué pasa con numpy.isclose frente a math.isclose?
Ambos existen; la versión de numpy maneja arrays. Elija un módulo por capa de código base para la coherencia.
¿Puedo usar Fraction para el dinero?
Posible para precios racionales pero incómodo para tablas de impuestos decimales. Decimal se ajusta mejor a las reglas humanas base-10.
¿Por qué las importaciones de CSV desvían los totales?
Comas de localización y análisis implícito de flotantes. Lea como cadena, analice a Decimal y luego cuantifique.
¿Cómo pruebo el redondeo?
Pruebas controladas por tabla en límites de medio céntimo, cantidades negativas y divisiones de impuestos tripartitas que deben sumar el total de la factura.
¿PyTorch 2.6+ usa float32 por defecto?
Muchos tensores usan float32 por defecto. No use flotantes de torch para matemáticas de contabilidad; mantenga las finanzas en rutas Decimal/int.
¿Cuándo está bien el flotante binario solo para la visualización de moneda?
Cuando los valores ya están cuantificados a céntimos y solo formatea para la interfaz de usuario, pero mantenga la contabilidad en céntimos enteros de extremo a extremo.
Relacionado
- Defectos de la canalización de datos - efectos secundarios de la coerción de tipos de datos
- Errores de codificación y Unicode - análisis de cadenas de números de localización
- Fragmentos de refactorización de depuración - refactorizaciones de flotante a Decimal
- Modelado de dominio - objetos de valor de dinero
- Herramientas de depuración - inspeccionar valores en pdb
Versiones de la pila: Esta página se escribió para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.