1. Introducción
Podría parecer que, si el ordenador «inteligente», entonces 0.1 + 0.2 debería ser simplemente 0.3. Pero no es exactamente así. Vamos a verlo con un ejemplo sencillo.
double x = 0.1;
double y = 0.2;
double sum = x + y;
System.out.println(sum); // ¿Qué imprimirá el programa?
Y ahora intenta compararlo con 0.3:
System.out.println(sum == 0.3); // ¿Y aquí será true o false?
Si ves false, ¡no te sorprendas!
La razón está en la representación de los números en memoria
Los ordenadores trabajan con números en sistema binario. Pero no todas las fracciones decimales pueden representarse como una fracción binaria finita, igual que 1/3 no puede escribirse exactamente como fracción decimal (0.333...). Por ejemplo, 0.1 en sistema binario — es una fracción infinita, y hay que «redondearla» para almacenarla.
En términos coloquiales, double a veces «hace como si» guardara tu número con exactitud, pero en realidad almacena solo un valor aproximado muy cercano.
2. ¿Qué «fallos» pueden aparecer al hacer aritmética con double?
Veamos ejemplos prácticos.
Ejemplo 1. La «magia» clásica de 0.1 + 0.2
double a = 0.1;
double b = 0.2;
double sum = a + b;
System.out.println(sum); // 0.30000000000000004
System.out.println(sum == 0.3); // false
El ordenador no mostró 0.3, sino 0.30000000000000004. La diferencia es pequeña, pero si trabajas, por ejemplo, con finanzas, ya es crítico.
Ejemplo 2. Suma iterativa
double result = 0;
for (int i = 0; i < 10; i++)
{
result += 0.1;
}
System.out.println(result); // 0.9999999999999999
Queríamos 1.0 — obtuvimos un poco menos. De nuevo, la razón es el redondeo dentro de double.
Por qué esto es importante en casos reales
Muchos piensan: «¿Qué más da? Es un pequeño error, ¡bah!». Consideremos un ejemplo del mundo de los pagos.
Supongamos que tu banca en línea suma 100 transacciones de 0.1 euros. Si tu programa «pierde» una cienmilésima de euro en cada iteración, a la escala del banco ya «perderás» dinero real. Entonces vendrá el contable y te preguntará: «¿Dónde está nuestro dinero?!»
3. Cómo comparar correctamente números de coma flotante
Como double a menudo no puede almacenar exactamente el valor que esperas, la comparación directa con == puede fallar. En su lugar se compara el valor absoluto de la diferencia con un número muy pequeño (epsilon).
Ejemplo de comparación con tolerancia
double a = 0.1 + 0.2;
double b = 0.3;
double epsilon = 0.000001;
if (Math.abs(a - b) < epsilon)
{
System.out.println("¡Casi igual!"); // Así es más seguro comparar
}
Aquí decimos: «Si la diferencia entre los números es menor que una millonésima, consideramos que son iguales».
Nota: la función Math.abs(value) devuelve el valor absoluto (módulo) del número que se le pasa.
4. Valores especiales de double: Infinity, NaN, -Infinity
El tipo double almacena no solo números, sino también valores especiales. Surgen en situaciones que un profesor de matemáticas prohíbe terminantemente a un estudiante.
Infinito (Infinity)
¿Qué ocurre si dividimos 1 entre 0?
double result = 1.0 / 0.0;
System.out.println(result); // Infinity
En Java (y en muchos lenguajes), dividir por 0 con double no lanza una excepción. En su lugar, el resultado se convierte en el valor especial «infinito positivo».
Menos infinito (-Infinity)
Si divides un número negativo entre 0, obtendrás infinito negativo:
double result = -1.0 / 0.0;
System.out.println(result); // -Infinity
«No es un número» (NaN — Not a Number)
Si haces algo extraño, por ejemplo, intentar calcular la raíz de un número negativo:
double result = Math.sqrt(-1);
System.out.println(result); // NaN
O el resultado de dividir 0.0 / 0.0:
double result = 0.0 / 0.0;
System.out.println(result); // NaN
NaN es «todo aquello que no sería un número en la vida real».
Comprobación de valores especiales
En Java hay funciones para comprobar los valores especiales:
System.out.println(Double.isInfinite(result)); // true si es infinito
System.out.println(Double.isNaN(result)); // true si es NaN
Tabla: cómo reacciona double ante operaciones inusuales
| Operación | Resultado | Qué almacena double |
|---|---|---|
|
Infinity | +∞ |
|
-Infinity | -∞ |
|
NaN | No es un número |
|
NaN | No es un número |
5. Errores típicos al trabajar con números de coma flotante
Error n.º 1: Comparar números en coma flotante con ==
La trampa más común: intentar comprobar si dos números en coma flotante calculados son iguales usando una comparación normal. Debido a la acumulación de errores de redondeo, casi siempre obtendrás un resultado inesperado. Usa siempre la comparación con tolerancia (epsilon).
Error n.º 2: NaN e Infinity inesperados en los cálculos
Si no controlas la división por cero o las raíces de números negativos, en el programa pueden aparecer NaN o Infinity y «contagiar» todos los cálculos posteriores. No olvides comprobar los valores sospechosos con Double.isNaN() y Double.isInfinite() — esto ayudará a evitar sorpresas desagradables.
Error n.º 3: Usar NaN como «marcador»
Algunos principiantes usan NaN como un valor «especial» para comprobar, por ejemplo, «si no se encuentra — devolvemos NaN». Pero recuerda: NaN es traicionero, y la comparación con == no funcionará. Comprueba solo mediante los métodos especiales.
Error n.º 4: Esperar una excepción al dividir entre 0.0
A diferencia de la división de enteros, dividir entre 0.0 con double no provoca un error, sino que devuelve Infinity o NaN. Esto puede llevar a errores silenciosos y difíciles de detectar — hay resultado, pero no es el que esperabas.
GO TO FULL VERSION