杰瑞科技汇

Java double如何精确比较大小?

下面我将详细解释为什么不能直接比较,以及推荐的最佳实践方法。

Java double如何精确比较大小?-图1
(图片来源网络,侵删)

为什么不能直接比较 double

double 在计算机中遵循 IEEE 754 标准,使用二进制科学计数法来存储浮点数,这导致了两个主要问题:

  1. 精度问题 很多十进制小数无法在二进制中精确表示,就像 1/3 在十进制中是 333... 无限循环一样,很多小数在二进制中也是无限循环的。

    • 经典例子1 + 0.2 在数学上等于 3,但在 Java 中:
      double a = 0.1;
      double b = 0.2;
      System.out.println(a + b == 0.3); // 输出 false

      因为 12 在计算机中的存储值是它们最接近的近似值,它们的和也是一个近似值,这个近似值并不完全等于 3 的近似值。

  2. 计算误差 在一系列的数学运算(如加减乘除)后,微小的误差会不断累积,导致最终结果与预期值有细微差别。

    Java double如何精确比较大小?-图2
    (图片来源网络,侵删)

正确的比较方法

核心思想是:不要判断两个 double 是否“相等”,而是判断它们的“差值”是否在一个可以接受的“误差范围”内。

使用绝对误差比较(最常用)

这种方法适用于判断两个数是否“足够接近”,我们定义一个很小的数 epsilon(ε),如果两个数的差的绝对值小于这个 epsilon,我们就认为它们相等。

如何选择 epsilon 这取决于你的应用场景和所需的精度,通常选择一个很小的值,如 1e-9 (0.000000001) 或 1e-15

实现代码:

Java double如何精确比较大小?-图3
(图片来源网络,侵删)
public class DoubleComparison {
    // 定义一个可接受的误差范围
    private static final double EPSILON = 1e-9;
    public static boolean isEqual(double a, double b) {
        // 如果差的绝对值小于 epsilon,则认为相等
        return Math.abs(a - b) < EPSILON;
    }
    public static void main(String[] args) {
        double d1 = 0.1 + 0.2;
        double d2 = 0.3;
        System.out.println("d1 == d2 (直接比较): " + (d1 == d2)); // false
        System.out.println("d1 ≈ d2 (绝对误差比较): " + isEqual(d1, d2)); // true
        double d3 = 1.000000001;
        double d4 = 1.000000002;
        System.out.println("d3 ≈ d4 (绝对误差比较): " + isEqual(d3, d4)); // true
        double d5 = 1.0;
        double d6 = 1.0001;
        System.out.println("d5 ≈ d6 (绝对误差比较): " + isEqual(d5, d6)); // false
    }
}

何时使用? 当两个数的数量级相近时,使用绝对误差比较非常合适,比较 00000010000002

使用相对误差比较(更健壮)

当两个数的数量级相差很大时,绝对误差比较可能会失效,比较 000000001000000002(差为 1e-9),与比较 0001(差为 001)时,同样的绝对误差 1e-9 的意义完全不同。

相对误差比较解决了这个问题,它计算的是差值相对于其中一个数的比例。

实现代码:

public class DoubleComparison {
    private static final double EPSILON = 1e-9;
    /**
     * 使用相对误差比较两个 double 是否相等
     * @param a 第一个数
     * @param b 第二个数
     * @return 如果相对差值小于 epsilon,则返回 true
     */
    public static boolean isEqualRelative(double a, double b) {
        // 处理两个数都为 0 的情况
        if (a == 0.0 && b == 0.0) {
            return true;
        }
        // 计算相对差值
        // 使用 (a - b) / ((a + b) / 2.0) 可以更好地处理一个数接近0的情况
        // 或者更简单的方式:Math.abs((a - b) / Math.max(Math.abs(a), Math.abs(b)))
        double diff = Math.abs(a - b);
        double scale = Math.max(Math.abs(a), Math.abs(b));
        // 防止 scale 为 0 (虽然上面已经处理了 a, b 都为 0 的情况,但更严谨)
        if (scale == 0.0) {
            return true;
        }
        return diff / scale < EPSILON;
    }
    public static void main(String[] args) {
        double d1 = 1.000000001;
        double d2 = 1.000000002;
        System.out.println("d1 ≈ d2 (相对误差): " + isEqualRelative(d1, d2)); // true
        double d3 = 1000000000.0;
        double d4 = 1000000000.001;
        // 绝对误差比较会失败,因为 0.001 > 1e-9
        System.out.println("d3 ≈ d4 (绝对误差): " + Math.abs(d3 - d4) < EPSILON); // false
        // 相对误差比较会成功,因为 0.001 / 1e9 = 1e-12 < 1e-9
        System.out.println("d3 ≈ d4 (相对误差): " + isEqualRelative(d3, d4)); // true
    }
}

何时使用? 当比较的数值范围很大时(从 0011,000,000.0),相对误差比较是更稳健的选择。

使用 Double.compare() 方法

Java 提供了 java.lang.Double.compare(double a, double b) 方法,这个方法可以安全地比较两个 double 的大小,而不会因为精度问题导致错误。

  • 返回值
    • a < b,返回负整数。
    • a == b,返回 0
    • a > b,返回正整数。

重要提示:这里的 a == b 的判断,仍然是基于二进制位模式的完全相等,而不是数学意义上的相等。它不适用于处理因计算误差导致的不相等

适用场景: 当你需要判断一个 double 是“小于”、“等于”还是“大于”另一个 double,并且你确信这两个值是直接从源(如常量、用户输入)获取而没有经过复杂计算时,可以使用它,在排序等场景中非常有用。

public class DoubleCompareMethod {
    public static void main(String[] args) {
        double d1 = 0.3;
        double d2 = 0.1 + 0.2;
        // 虽然数学上 d1 应该等于 d2,但 compare 会返回非零
        int result = Double.compare(d1, d2);
        System.out.println("Double.compare(0.3, 0.1+0.2): " + result); // 返回一个非零整数,1 或 -1
        // 比较明确的数值
        double d3 = 1.5;
        double d4 = 2.5;
        System.out.println("Double.compare(1.5, 2.5): " + Double.compare(d3, d4)); // -1
        System.out.println("Double.compare(2.5, 1.5): " + Double.compare(d4, d3)); // 1
        System.out.println("Double.compare(1.5, 1.5): " + Double.compare(d3, d3)); // 0
    }
}

总结与最佳实践

场景 推荐方法 原因
判断两个 double 是否“相等” 绝对误差比较 Math.abs(a - b) < EPSILON 这是最常见的需求,直接处理了浮点数精度问题。
数值范围很大时判断“相等” 相对误差比较 Math.abs(a-b)/scale < EPSILON 避免了因数值大小差异导致的误判,更加健壮。
需要比较大小(<, >, ) Double.compare(a, b) 官方提供的安全比较方法,适用于排序或需要明确大小关系的场景。注意:这里的 是严格二进制相等。
处理金融、货币等高精度计算 使用 BigDecimal 如果业务要求绝对精确(如财务计算),doublefloat 都不应该使用,而应使用 java.math.BigDecimal

代码示例:封装一个健壮的比较工具类

import java.math.BigDecimal;
public class DoubleUtils {
    // 默认的误差范围
    private static final double DEFAULT_EPSILON = 1e-9;
    /**
     * 判断两个 double 是否“相等”(使用绝对误差)
     */
    public static boolean equals(double a, double b) {
        return equals(a, b, DEFAULT_EPSILON);
    }
    /**
     * 判断两个 double 是否“相等”(使用自定义绝对误差)
     */
    public static boolean equals(double a, double b, double epsilon) {
        if (Double.isNaN(a) || Double.isNaN(b)) {
            return false; // NaN 与任何数(包括自身)都不相等
        }
        return Math.abs(a - b) < epsilon;
    }
    /**
     * 判断两个 double 是否“相等”(使用相对误差)
     */
    public static boolean equalsRelative(double a, double b) {
        return equalsRelative(a, b, DEFAULT_EPSILON);
    }
    /**
     * 判断两个 double 是否“相等”(使用自定义相对误差)
     */
    public static boolean equalsRelative(double a, double b, double epsilon) {
        if (Double.isNaN(a) || Double.isNaN(b)) {
            return false;
        }
        if (a == b) { // 处理无穷大和0的情况
            return true;
        }
        double diff = Math.abs(a - b);
        double scale = Math.max(Math.abs(a), Math.abs(b));
        return diff / scale < epsilon;
    }
    /**
     * 判断 a 是否大于 b
     */
    public static boolean gt(double a, double b) {
        return Double.compare(a, b) > 0;
    }
    /**
     * 判断 a 是否大于等于 b
     */
    public static boolean gte(double a, double b) {
        return Double.compare(a, b) >= 0;
    }
    /**
     * 判断 a 是否小于 b
     */
    public static boolean lt(double a, double b) {
        return Double.compare(a, b) < 0;
    }
    /**
     * 判断 a 是否小于等于 b
     */
    public static boolean lte(double a, double b) {
        return Double.compare(a, b) <= 0;
    }
    /**
     * 高精度计算(推荐用于财务等场景)
     */
    public static BigDecimal add(double a, double b) {
        return BigDecimal.valueOf(a).add(BigDecimal.valueOf(b));
    }
}

记住这个黄金法则:永远不要直接用 来比较两个 double 是否相等,除非你100%确定它们的行为。

分享:
扫描分享到社交APP
上一篇
下一篇