DEV Community

 AppInitDev
AppInitDev

Posted on

Nunca uses float para dinero: un presupuesto reproducible con Decimal

Nunca uses float para dinero: un presupuesto reproducible con Decimal

Los errores de redondeo parecen pequeños hasta que se acumulan en reportes, presupuestos y saldos. El modelo numérico importa incluso en una herramienta personal sin conexiones bancarias.

El problema y el alcance del ejemplo

La ficha de Vault Wallet: Control Gastos resume su función así: «Gestor de gastos y planificador de presupuesto para tus finanzas personales.» Vault Wallet anuncia registro de gastos, presupuestos y manejo de varias monedas. El ejemplo toma una decisión que se puede evaluar sin conocer el código privado de la app: usar decimales explícitos y no sumar divisas diferentes.

Cómo reproducir la idea

Necesitas Python 3; pega este código en presupuesto.py y ejecútalo con python presupuesto.py.

from decimal import Decimal

movimientos = [
    {"categoria": "comida", "moneda": "MXN", "importe": "129.90"},
    {"categoria": "comida", "moneda": "MXN", "importe": "70.10"},
    {"categoria": "transporte", "moneda": "MXN", "importe": "35.50"},
    {"categoria": "servicios", "moneda": "USD", "importe": "9.99"},
]
limites = {("comida", "MXN"): Decimal("250.00")}
totales = {}
for m in movimientos:
    clave = (m["categoria"], m["moneda"])
    totales[clave] = totales.get(clave, Decimal("0")) + Decimal(m["importe"])
for clave, gastado in sorted(totales.items()):
    limite = limites.get(clave)
    print(clave, f"gastado={gastado:.2f}",
          f"restante={limite-gastado:.2f}" if limite is not None else "sin límite")
Enter fullscreen mode Exit fullscreen mode

Qué demuestra el código

El total en comida es exactamente 200.00 MXN. Si usamos números binarios de punto flotante pueden aparecer representaciones intermedias como 0.30000000000000004. Agrupar por (categoría, moneda) evita que un total en pesos incorpore dólares silenciosamente.

Pruebas recomendadas

Cambia el segundo gasto a 120.10: el presupuesto queda en negativo y puede convertirse en alerta. Agrega otra operación en USD: la suma se conserva separada. Prueba también importes negativos para devoluciones, definiendo explícitamente cómo deben tratarse.

Límites que conviene reconocer

No hay persistencia, impuestos, tasas de cambio, sincronización ni reglas fiscales. En producción hay que fijar políticas de redondeo, moneda principal, fechas, devoluciones y versiones de tasas antes de generar reportes.

Qué aprendemos

No todo se arregla con una pantalla de presupuesto. La precisión monetaria y la semántica de cada operación forman parte del modelo de datos.

Contexto y enlaces del autor

El tema se relaciona con las funciones declaradas públicamente de Vault Wallet: Control Gastos, una aplicación Android de AppInitDev. El código anterior es un ejemplo independiente: no se ha inspeccionado el repositorio privado ni se afirma que sea la implementación interna de la app. La referencia aparece al final para ofrecer contexto, no como requisito para completar el tutorial.

APPS

Referencia técnica

Python: decimal

Top comments (1)

Collapse
 
florian131313 profile image
G •

Sumar Decimal armados desde cadenas como "129.90" sí es exacto. El problema aparece cuando se divide una cuenta o se aplica un impuesto: el resultado trae más decimales que la moneda. Hay que pasar quantize(Decimal("0.01")) antes de sumarlo de nuevo. Si no, esos dígitos entran al siguiente total, y el {:.2f} solo los esconde al imprimir.

Ojo con la entrada: Decimal(float(monto)) se queda con el error binario. El monto tiene que llegar como cadena.