Java Language Comparando valores de punto flotante


Ejemplo

Debe tener cuidado al comparar valores de punto float ( float o double ) utilizando operadores relacionales: == != , < Y así sucesivamente. Estos operadores dan resultados de acuerdo con las representaciones binarias de los valores de punto flotante. Por ejemplo:

public class CompareTest {
    public static void main(String[] args) {
        double oneThird = 1.0 / 3.0;
        double one = oneThird * 3;
        System.out.println(one == 1.0);      // prints "false"
    }
}

El cálculo oneThird ha introducido un pequeño error de redondeo, y cuando multiplicamos oneThird por 3 obtenemos un resultado que es ligeramente diferente a 1.0 .

Este problema de representaciones inexactas es más grave cuando intentamos mezclar el double y el float en los cálculos. Por ejemplo:

public class CompareTest2 {
    public static void main(String[] args) {
        float floatVal = 0.1f;
        double doubleVal = 0.1;
        double doubleValCopy = floatVal;

        System.out.println(floatVal);      // 0.1
        System.out.println(doubleVal);     // 0.1
        System.out.println(doubleValCopy); // 0.10000000149011612
        
        System.out.println(floatVal == doubleVal); // false
        System.out.println(doubleVal == doubleValCopy); // false
    }
}

Las representaciones de punto flotante utilizadas en Java para los tipos float y double tienen un número limitado de dígitos de precisión. Para el tipo float , la precisión es de 23 dígitos binarios o aproximadamente 8 dígitos decimales. Para el tipo double , es de 52 bits o alrededor de 15 dígitos decimales. Además de eso, algunas operaciones aritméticas introducirán errores de redondeo. Por lo tanto, cuando un programa compara valores de punto flotante, es una práctica estándar definir un delta aceptable para la comparación. Si la diferencia entre los dos números es menor que el delta, se considera que son iguales. Por ejemplo

if (Math.abs(v1 - v2) < delta)

Ejemplo de comparación de Delta:

public class DeltaCompareExample {

    private static boolean deltaCompare(double v1, double v2, double delta) {
        // return true iff the difference between v1 and v2 is less than delta
        return Math.abs(v1 - v2) < delta;
    }
    
    public static void main(String[] args) {
        double[] doubles = {1.0, 1.0001, 1.0000001, 1.000000001, 1.0000000000001};
        double[] deltas = {0.01, 0.00001, 0.0000001, 0.0000000001, 0};

        // loop through all of deltas initialized above
        for (int j = 0; j < deltas.length; j++) {
            double delta = deltas[j];
            System.out.println("delta: " + delta);

            // loop through all of the doubles initialized above
            for (int i = 0; i < doubles.length - 1; i++) {
                double d1 = doubles[i];
                double d2 = doubles[i + 1];
                boolean result = deltaCompare(d1, d2, delta);

                System.out.println("" + d1 + " == " + d2 + " ? " + result);
                
            }

            System.out.println();
        }
    }
}

Resultado:

delta: 0.01
1.0 == 1.0001 ? true
1.0001 == 1.0000001 ? true
1.0000001 == 1.000000001 ? true
1.000000001 == 1.0000000000001 ? true

delta: 1.0E-5
1.0 == 1.0001 ? false
1.0001 == 1.0000001 ? false
1.0000001 == 1.000000001 ? true
1.000000001 == 1.0000000000001 ? true

delta: 1.0E-7
1.0 == 1.0001 ? false
1.0001 == 1.0000001 ? false
1.0000001 == 1.000000001 ? true
1.000000001 == 1.0000000000001 ? true

delta: 1.0E-10
1.0 == 1.0001 ? false
1.0001 == 1.0000001 ? false
1.0000001 == 1.000000001 ? false
1.000000001 == 1.0000000000001 ? false

delta: 0.0
1.0 == 1.0001 ? false
1.0001 == 1.0000001 ? false
1.0000001 == 1.000000001 ? false
1.000000001 == 1.0000000000001 ? false

También para la comparación de tipos primitivos double y float se puede usar el método de compare estática del tipo de boxeo correspondiente. Por ejemplo:

double a = 1.0;
double b = 1.0001;

System.out.println(Double.compare(a, b));//-1
System.out.println(Double.compare(b, a));//1

Finalmente, determinar qué deltas son más apropiadas para una comparación puede ser complicado. Un enfoque comúnmente utilizado es seleccionar valores delta que, según nuestra intuición, son correctos. Sin embargo, si conoce la escala y la precisión (verdadera) de los valores de entrada y los cálculos realizados, puede ser posible establecer límites matemáticamente sólidos sobre la precisión de los resultados y, por lo tanto, para los deltas. (Hay una rama formal de Matemáticas conocida como Análisis Numérico que solía enseñarse a científicos computacionales que cubrían este tipo de análisis).