Java Language Confronto tra valori in virgola mobile

Esempio

È necessario prestare attenzione quando si confrontano valori a virgola mobile ( float o double ) utilizzando operatori relazionali: == != , < E così via. Questi operatori danno risultati in base alle rappresentazioni binarie dei valori in virgola mobile. Per esempio:

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"
    }
}

Il calcolo del oneThird ha introdotto un piccolo errore di arrotondamento e quando moltiplichiamo il oneThird per 3 otteniamo un risultato leggermente diverso da 1.0 .

Questo problema di rappresentazioni inesatte è più marcato quando si tenta di combinare il double e il float nei calcoli. Per esempio:

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
    }
}

Le rappresentazioni in virgola mobile utilizzate in Java per i tipi float e double hanno un numero limitato di cifre di precisione. Per il tipo float , la precisione è di 23 cifre binarie o di circa 8 cifre decimali. Per il double tipo, è 52 bit o circa 15 cifre decimali. Inoltre, alcune operazioni aritmetiche introdurranno errori di arrotondamento. Pertanto, quando un programma confronta i valori in virgola mobile, pratica standard per definire un delta accettabile per il confronto. Se la differenza tra i due numeri è inferiore al delta, vengono considerati uguali. Per esempio

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

Esempio di confronto 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();
        }
    }
}

Risultato:

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

Anche per il confronto dei tipi primitivi a double e float possibile utilizzare il metodo di compare statico del tipo di boxing corrispondente. Per esempio:

double a = 1.0;
double b = 1.0001;

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

Infine, determinare quali delta sono più appropriati per un confronto può essere complicato. Un approccio comunemente usato è quello di selezionare valori delta che secondo la nostra intuizione sono giusti. Tuttavia, se si conoscono la scala e l'accuratezza (vera) dei valori di input e i calcoli eseguiti, potrebbe essere possibile ottenere limiti matematicamente validi sulla precisione dei risultati, e quindi per i delta. (Esiste un ramo formale della matematica noto come analisi numerica che veniva insegnato agli scienziati computazionali che coprivano questo tipo di analisi).