¿Cuánto es 9999999999999999.0 - 9999999999999998.0? Debería ser 1, pero en la mayoría de los lenguajes el resultado es 2. Geo Carncross hizo la prueba en una veintena de ellos, y la tabla de arriba resume lo que obtuvo. Aquí lo comprobamos en Java y vemos por qué pasa.
También te puede interesar
En Java
double a = 9999999999999999.0;
double b = 9999999999999998.0;
IO.println("a: " + a);
IO.println("b: " + b);
IO.println("a - b: " + (a - b));
IO.println("a exacto: " + new BigDecimal(a));
IO.println("b exacto: " + new BigDecimal(b));
IO.println("separación entre doubles cerca de a: " + Math.ulp(a));
IO.println("2^53 = " + (long) Math.pow(2, 53));salidaa: 1.0E16 b: 9.999999999999998E15 a - b: 2.0 a exacto: 10000000000000000 b exacto: 9999999999999998 separación entre doubles cerca de a: 2.0 2^53 = 9007199254740992
La segunda línea de la salida ya da la pista: a se imprime como 1.0E16, es decir, 10000000000000000. El número que escribimos no es el que se guardó. new BigDecimal(a) muestra el valor exacto que hay en memoria, y la resta, con esos valores, sí está bien hecha: 10000000000000000 − 9999999999999998 = 2.
Por qué no cabe
Un double ocupa 64 bits: 1 para el signo, 11 para el exponente y 52 para la mantisa, que con un bit implícito dan 53 bits de precisión. Con 53 bits se pueden representar exactamente todos los enteros hasta 253 = 9,007,199,254,740,992. Nuestros números son mayores.
Por encima de ese límite, los double ya no están en todos los enteros: entre 253 y 254 van de dos en dos, que es lo que dice Math.ulp(a). 9999999999999998 es par y cae justo en uno. 9999999999999999 es impar, queda exactamente a medio camino entre 9999999999999998 y 10000000000000000, y la regla de redondeo de IEEE 754 (al vecino «par») lo lleva a 10000000000000000.
No es un fallo de Java: es el formato que usan casi todos los lenguajes y los procesadores. Por eso la mayoría de la tabla dice 2.
El caso de todos los días: 0.1 + 0.2
No hace falta irse a números tan grandes. Con decimales pequeños pasa algo parecido, porque la mayoría no tienen representación exacta en binario, igual que 1/3 no la tiene en decimal:
IO.println("0.1 + 0.2 = " + (0.1 + 0.2));
IO.println("0.1 + 0.2 == 0.3 -> " + (0.1 + 0.2 == 0.3));
IO.println("0.1 exacto: " + new BigDecimal(0.1));
IO.println("Math.abs((0.1 + 0.2) - 0.3) < 1e-9 -> " + (Math.abs((0.1 + 0.2) - 0.3) < 1e-9));salida0.1 + 0.2 = 0.30000000000000004 0.1 + 0.2 == 0.3 -> false 0.1 exacto: 0.1000000000000000055511151231257827021181583404541015625 Math.abs((0.1 + 0.2) - 0.3) < 1e-9 -> true
El 0.1 que se guarda es un poquito mayor que 0.1, y al sumar se acumulan esos errores. De ahí la regla: nunca compares double con ==. Compara la diferencia con un margen de tolerancia adecuado a tu problema.
Cuando hace falta exactitud
IO.println("new BigDecimal(\"0.1\").add(new BigDecimal(\"0.2\")) = " + new BigDecimal("0.1").add(new BigDecimal("0.2")));
IO.println("new BigDecimal(0.1).add(new BigDecimal(0.2)) = " + new BigDecimal(0.1).add(new BigDecimal(0.2)));
IO.println("9999999999999999L - 9999999999999998L = " + (9999999999999999L - 9999999999999998L));
double suma = 0;
for (int i = 0; i < 10; i++) suma += 0.1;
IO.println("0.1 sumado diez veces = " + suma);salidanew BigDecimal("0.1").add(new BigDecimal("0.2")) = 0.3
new BigDecimal(0.1).add(new BigDecimal(0.2)) = 0.3000000000000000166533453693773481063544750213623046875
9999999999999999L - 9999999999999998L = 1
0.1 sumado diez veces = 0.9999999999999999Dos detalles importantes. BigDecimal se crea a partir de una cadena: new BigDecimal(0.1) recibe el double ya redondeado y conserva el error con todos sus decimales. Y la resta original, hecha con long, da 1, porque un long guarda enteros exactos hasta unos 9.2 trillones.
| Para… | Usa | Por qué |
|---|---|---|
| Medidas, física, gráficos, estadística | double | Rápido, y un error en el decimal 16 no importa |
| Dinero | BigDecimal creado desde texto, o long en céntimos | Los céntimos tienen que cuadrar siempre |
| Contadores e identificadores grandes | long | Exacto hasta 9,223,372,036,854,775,807 |
Comparar dos double | Math.abs(x - y) < tolerancia | == falla con resultados de operaciones |
La tabla de la imagen está tomada del experimento de Geo Carncross, que sigue en línea con algunos lenguajes más.
Entrada publicada en 2019 y revisada en octubre de 2026. Todo el código se compiló y se ejecutó con Java 25 LTS en un contenedor limpio; las salidas están copiadas de esa ejecución.










