比較浮點值

使用關係運算子比較浮點值(floatdouble)時應該小心:==!=< 等。這些運算子根據浮點值的二進位制表示給出結果。例如:

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

計算 oneThird 引入了一個很小的舍入誤差,當我們將 oneThird 乘以 3 時,得到的結果與 1.0 略有不同。

當我們嘗試在計算中混合 doublefloat 時,這種不精確表示的問題更加明顯。例如:

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

Java 中用於 floatdouble 型別的浮點表示具有有限的精度位數。對於 float 型別,精度是 23 個二進位制數字或大約 8 個十進位制數字。對於 double 型別,它是 52 位或大約 15 個十進位制數字。最重要的是,一些算術運算會引入舍入誤差。因此,當程式比較浮點值時,標準做法是為比較定義可接受的增量。如果兩個數字之間的差異小於 delta,則認為它們相等。例如

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

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

結果:

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

另外,為了比較 doublefloat 原始型別,可以使用相應拳擊型別的靜態 compare 方法。例如:

double a = 1.0;
double b = 1.0001;

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

最後,確定哪種增量最適合進行比較可能會非常棘手。一種常用的方法是選擇 delta 值,這是我們的直覺所說的正確。但是,如果你知道輸入值的比例和(真實)準確度以及執行的計算,則可能會在結果的準確性上提出數學上的聲音界限,從而得出增量。 (有一個稱為數值分析的正式數學分支,曾經被教給計算科學家,涵蓋了這種分析。)