Con double, 0.1 + 0.2 da 0.30000000000000004, y en una factura eso es un error. Para dinero, Java tiene BigDecimal: números decimales exactos, con el redondeo bajo tu control. Aquí calculamos una factura con IVA y la mostramos en pesos mexicanos.
También te puede interesar
Una factura con IVA
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.util.List;
import java.util.Locale;
record Linea(String concepto, int cantidad, BigDecimal precio) {
BigDecimal importe() { return precio.multiply(BigDecimal.valueOf(cantidad)); }
}
final BigDecimal IVA = new BigDecimal("0.16");
void main() {
var lineas = List.of(
new Linea("Teclado", 2, new BigDecimal("449.90")),
new Linea("Mouse", 1, new BigDecimal("199.99")),
new Linea("Cable USB-C", 3, new BigDecimal("89.50")));
BigDecimal subtotal = lineas.stream().map(Linea::importe).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal iva = subtotal.multiply(IVA).setScale(2, RoundingMode.HALF_UP);
BigDecimal total = subtotal.add(iva);
var pesos = NumberFormat.getCurrencyInstance(Locale.of("es", "MX"));
lineas.forEach(l -> IO.println("%-12s %2d x %10s = %11s".formatted(
l.concepto(), l.cantidad(), pesos.format(l.precio()), pesos.format(l.importe()))));
IO.println("Subtotal: " + pesos.format(subtotal));
IO.println("IVA 16 %: " + pesos.format(iva));
IO.println("Total: " + pesos.format(total));
}salidaTeclado 2 x $449.90 = $899.80 Mouse 1 x $199.99 = $199.99 Cable USB-C 3 x $89.50 = $268.50 Subtotal: $1,368.29 IVA 16 %: $218.93 Total: $1,587.22
Las reglas de oro
- Crea los valores desde texto:
new BigDecimal("449.90"). Desde undoubleheredas su error:new BigDecimal(0.1)vale 0.1000000000000000055511151231257827… - Los
BigDecimalno cambian:addomultiplydevuelven un número nuevo.subtotal.add(iva);suelto no hace nada; hay que guardar el resultado. - Redondea a propósito con
setScale(2, RoundingMode.HALF_UP), el redondeo «de la escuela» (0.5 sube).HALF_EVEN, el de los bancos, reparte mejor los errores en sumas largas. - Compara con
compareTo, no conequals: paraequals, 2.0 y 2.00 son distintos (está en las rarezas).
Dividir siempre con escala
import java.math.BigDecimal;
import java.math.RoundingMode;
void main() {
var cuenta = new BigDecimal("1000.00");
var personas = BigDecimal.valueOf(3);
try {
cuenta.divide(personas);
} catch (ArithmeticException e) {
IO.println("sin escala: " + e.getMessage());
}
var cadaUno = cuenta.divide(personas, 2, RoundingMode.HALF_UP);
var sobra = cuenta.subtract(cadaUno.multiply(personas));
IO.println("cada uno paga " + cadaUno + " y sobran " + sobra);
}salidasin escala: Non-terminating decimal expansion; no exact representable decimal result. cada uno paga 333.33 y sobran 0.01
1000 entre 3 no tiene un resultado decimal exacto, y BigDecimal se niega a inventarlo: hay que decirle cuántos decimales y cómo redondear. Y ojo con el centavo que sobra o falta al repartir: en un sistema de cobros, alguien tiene que llevarlo.
Todo el código se compiló y se ejecutó con Java 25 LTS en un contenedor limpio antes de publicar esta página; las salidas están copiadas de esa ejecución.