9. Redondeo, precisión decimal y errores de cálculo monetario

Un resultado con muchos decimales puede ser matemáticamente válido, pero una aplicación financiera necesita reglas explícitas para calcular, redondear y presentar dinero.

9.1 Por qué importan los centavos

Una diferencia de un centavo parece pequeña, pero puede repetirse en miles de operaciones, impedir que un total coincida con sus partes o producir saldos que no cierran. El problema no se resuelve mostrando menos decimales: debemos decidir cómo se calculan y almacenan los valores.

Tres conceptos deben separarse: precisión interna, regla de redondeo y formato de presentación.

9.2 Los números decimales en JavaScript

JavaScript utiliza normalmente números de punto flotante binario. Muchas fracciones decimales no tienen una representación binaria finita, del mismo modo que 1/3 no tiene una representación decimal finita.

0.1 + 0.2 = 0.30000000000000004

La pequeña diferencia proviene de la representación, no de una suma escrita incorrectamente.

const resultado = 0.1 + 0.2;
console.log(resultado);
console.log(resultado === 0.3);

9.3 Error de representación y error de modelo

Un error de representación es una pequeña diferencia causada por el formato numérico. Un error de modelo surge cuando elegimos una relación equivocada o usamos datos incompatibles. Son problemas distintos.

Agregar decimales no corrige una fórmula equivocada. Del mismo modo, una fórmula correcta puede necesitar una estrategia numérica adecuada para trabajar con dinero.

9.4 Calcular, contabilizar y mostrar

Valor calculado

Puede conservar todos los decimales disponibles durante las operaciones.

Valor contabilizado

Aplica la regla definida para registrar unidades monetarias.

Valor mostrado

Usa separadores, símbolo de moneda y cantidad visible de decimales.

Intl.NumberFormat cambia la presentación, pero no modifica el número almacenado.

9.5 Redondear a una cantidad de decimales

Para redondear a d decimales, escalamos, redondeamos y volvemos a la escala original:

factor = 10ᵈ
resultado = redondear(valor × factor) / factor
function redondear(valor, decimales = 2) {
  const factor = 10 ** decimales;
  return Math.round((valor + Number.EPSILON) * factor) / factor;
}

console.log(redondear(12.345, 2));

Sumar Number.EPSILON ayuda en algunos casos positivos cercanos a un límite, pero no constituye una solución decimal universal. La regla exigida por la operación debe definirse y probarse.

9.6 Métodos de aproximación

Resultados sobre 12,349 al trabajar con dos decimales.
MétodoOperación JavaScriptResultado
Redondeo al más cercanoMath.round(12,349 × 100) / 10012,35
TruncamientoMath.trunc(12,349 × 100) / 10012,34
Hacia arribaMath.ceil(12,349 × 100) / 10012,35
Hacia abajoMath.floor(12,349 × 100) / 10012,34

Con valores negativos, truncar y redondear hacia abajo producen resultados diferentes. No debemos usar un método sin conocer la convención requerida.

9.7 Reglas de desempate

Cuando el dígito descartado se encuentra exactamente a mitad de camino pueden utilizarse criterios diferentes: mitad hacia arriba, mitad alejándose de cero o mitad al par, entre otros.

No existe una única regla financiera universal: contratos, normas contables, monedas y sistemas pueden exigir criterios distintos. El programa debe documentar e implementar la regla aplicable.

9.8 Redondear al final o en cada etapa

Si un cálculo se repite, redondear en cada etapa modifica la base de la siguiente. Conservar precisión y redondear solo al final puede producir otro resultado.

método A: calcular todas las etapas → redondear al final
método B: calcular una etapa → redondear → usar ese resultado

Ningún método es automáticamente correcto en todos los casos. La elección forma parte de la especificación del problema.

9.9 Formatear no es redondear el dato

toFixed(2) devuelve un texto con dos decimales. Intl.NumberFormat devuelve un texto localizado. Ninguno debe confundirse con una transformación del valor almacenado.

const valor = 1234.5;
const textoFijo = valor.toFixed(2);
const textoMoneda = new Intl.NumberFormat("es-AR", {
  style: "currency",
  currency: "ARS"
}).format(valor);

console.log(typeof valor, valor);
console.log(typeof textoFijo, textoFijo);
console.log(textoMoneda);

9.10 Trabajar con unidades menores enteras

Para operaciones que solo requieren centavos exactos, podemos almacenar cantidades como enteros: $10,25 se representa mediante 1025 centavos. Así, sumar y restar centavos evita ciertas aproximaciones binarias.

const precioCentavos = 1025;
const cantidad = 3;
const totalCentavos = precioCentavos * cantidad;
const totalPesos = totalCentavos / 100;

console.log(totalPesos.toFixed(2));

Esta estrategia no elimina la necesidad de reglas cuando porcentajes, divisiones o conversiones producen fracciones menores que un centavo.

9.11 Comparar números con tolerancia

Por la representación de punto flotante, dos resultados conceptualmente iguales pueden diferir en una cantidad diminuta. Para pruebas numéricas podemos comparar su diferencia con una tolerancia adecuada a la escala:

function casiIguales(a, b, tolerancia = 1e-9) {
  return Math.abs(a - b) <= tolerancia;
}

console.log(casiIguales(0.1 + 0.2, 0.3));

La tolerancia técnica no reemplaza una regla monetaria de centavos. Son controles con finalidades diferentes.

9.12 Simulador de precisión acumulada

La herramienta aplica un porcentaje al saldo de cada etapa y compara tres políticas: precisión completa, redondeo a centavos y truncamiento a centavos en cada paso.

Tres políticas de cálculo

Observá cuándo aparecen diferencias y cómo se acumulan.

Precisión completa, mostrada a centavos
Redondeo en cada etapa
Truncamiento en cada etapa
Los resultados también se presentan en la tabla.
Evolución según la política elegida.
EtapaPrecisión completaRedondeadoTruncadoDiferencia máxima

Diferencia final entre redondeo y truncamiento: —

Calculando comparación.

9.13 Errores frecuentes

  • Suponer que mostrar dos decimales cambia el valor interno.
  • Usar toFixed y olvidar que devuelve texto.
  • Redondear en lugares distintos sin documentarlo.
  • Comparar resultados de punto flotante mediante igualdad estricta en pruebas sensibles.
  • Distribuir un total redondeando cada parte sin comprobar la suma.
  • Suponer que guardar centavos enteros resuelve operaciones que generan fracciones de centavo.

9.14 Ejercicio propuesto

Se deben distribuir exactamente $100,00 entre 3 personas con precisión de centavos. Dividir y asignar $33,33 a cada una produce un total de $99,99.

  1. Calculá el total en centavos.
  2. Obtené el cociente entero y el resto de la división.
  3. Distribuí el centavo restante sin alterar el total.
  4. Implementá una función que funcione con cualquier total entero de centavos y cantidad positiva de participantes.
Ver solución y código

$100,00 son 10.000 centavos. La división entera entre 3 da 3.333 centavos y deja un resto de 1. Dos personas reciben $33,33 y una recibe $33,34. La suma es exactamente $100,00.

function distribuirCentavos(totalCentavos, personas) {
  if (!Number.isInteger(totalCentavos) || totalCentavos < 0 || !Number.isInteger(personas) || personas <= 0) {
    throw new Error("Los datos deben ser enteros válidos");
  }
  const base = Math.floor(totalCentavos / personas);
  const resto = totalCentavos % personas;
  return Array.from({ length: personas }, (_, indice) =>
    base + (indice < resto ? 1 : 0)
  );
}

const partes = distribuirCentavos(10000, 3);
console.log(partes.map(centavos => (centavos / 100).toFixed(2)));
console.log("Suma:", partes.reduce((a, b) => a + b, 0));

9.15 Ideas para recordar

  • Los números decimales pueden tener aproximaciones en punto flotante binario.
  • Precisión interna, redondeo contable y formato visual son decisiones distintas.
  • Redondear en cada etapa puede cambiar los resultados posteriores.
  • Los centavos enteros son útiles para sumas y distribuciones exactas.
  • La regla de redondeo debe formar parte explícita de la especificación.

En el próximo tema estudiaremos el valor del dinero en el tiempo.