Comparación

Debido a los errores de redondeo, la mayoría de los números de punto flotante terminan siendo ligeramente imprecisos. Mientras esta imprecisión se mantenga pequeña, normalmente se puede ignorar. Sin embargo, esto significa también que números que se espera que sean iguales (por ejemplo al calcular el mismo resultado utilizando distintos métodos correctos) a veces difieren levemente, y una simple prueba de igualdad falla. Por ejemplo:

	float a = 0.15 + 0.15
	float b = 0.1 + 0.2
	if(a == b) // ¡Puede ser falso!
	if(a >= b) // ¡También puede ser falso!

No usar márgenes de error absolutos

La solución es comprobar no si los números son exactamente iguales, sino si su diferencia es muy pequeña. El margen de error frente al que se compara esta diferencia normalmente se llama epsilon. En su forma más simple:

	if(Math.abs(a-b) < 0.00001) // Mal ─ no hacer esto

Esto es una mala forma de hacerlo porque un epsilon fijo elegido porque «parece pequeño» podría perfectamente ser demasiado grande cuando los números que se comparan son también muy pequeños. La comparación devolvería «verdadero» para números muy diferentes. Y cuando los números son muy grandes, el epsilon puede acabar siendo más pequeño que el mínimo error de redondeo, por lo que la comparación siempre devolvería «falso». Por tanto, es necesario ver si el error relativo es menor que epsilon:

	if(Math.abs((a-b)/b) < 0.00001) // ¡Todavía no es correcto!

Vigila los casos límite

Hay algunos casos especiales importantes para los que esto falla:

Además, el resultado no es conmutativo (nearlyEquals(a,b) no es siempre lo mismo que nearlyEquals(b,a)). Para solucionar estos problemas, el código tiene que ser mucho más complejo, así que necesitamos meterlo en una función:

    public static boolean nearlyEqual(float a, float b, float epsilon)
    {
        final float absA = Math.abs(a);
        final float absB = Math.abs(b);
        final float diff = Math.abs(a - b);

        if (a == b) { // Atajo, maneja los infinitos
            return true;
        } else if (a * b == 0) { // a o b o ambos son cero
            // El error relativo no es importante aquí
            return diff < (epsilon * epsilon);
        } else { // Usar el error relativo
            return diff / (absA + absB) < epsilon;
        }
    }

Este método pasa las pruebas para muchos casos especiales importantes, pero como puedes ver, utiliza cierta lógica no trivial. En particular, tiene que utilizar una definición totalmente distinta del margen de error cuando a o b son cero, porque la definición clásica del error relativo es inútil en esos casos.

Hay algunos casos en los que el método de arriba todavía produce resultados inesperados (concretamente, es mucho más estricto cuando un valor es casi cero que cuando es exactamente cero), y algunas de esas pruebas para las que fue desarrollado probablemente especifica un comportamiento que no es apropiado para algunas aplicaciones. Antes de usarlo, ¡asegúrate de que es adecuado para tu aplicación!

Comparando valores de punto flotante como enteros

Hay una alternativa a aplicar toda esta complejidad conceptual a una tarea aparentemente tan sencilla: en lugar de comparar a y b como números reales, podemos concebirlos como pasos discretos y definir el margen de error como el número máximo de valores de punto flotante posibles entre esos dos números.

Esto es conceptualmente muy evidente y fácil y tiene la ventaja de que escala implícitamente el margen de error relativo con la magnitud de los valores. Técnicamente es un poco más complejo, pero no mucho más de lo que puedas pensar, porque los números de punto flotante del IEEE 754 están diseñados para mantener su orden cuando sus secuencias de bits se interpretan como enteros.

Sin embargo, este método requiere que el lenguaje de programación soporte conversión entre valores de punto flotante y secuencias de bits enteras. Lee el artículo en inglés Comparing floating-point numbers para más detalles.

© Publicado en http://puntoflotante.org/ bajo una licencia Creative Commons Atribución Unported (BY). Original en inglés por Michael Borgwardt en http://floating-point-gui.de/.

Fork me on GitHub