CodeGym /Cursos /JAVA 25 SELF /Problemas de precisión y valores especiales

Problemas de precisión y valores especiales

JAVA 25 SELF
Nivel 6 , Lección 3
Disponible

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?
Suma de números de coma flotante: esperamos 0.3, ¿y qué ocurre en realidad?

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
1.0 / 0.0
Infinity +∞
-1.0 / 0.0
-Infinity -∞
0.0 / 0.0
NaN No es un número
Math.sqrt(-1)
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.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION