在 Java 中比较 double 类型的大小需要特别注意,因为浮点数在计算机中的存储方式(遵循 IEEE 754 标准)以及精度问题会导致直接使用 或比较运算符(>, <, >=, <=)出现意想不到的结果。

核心问题:浮点数的精度误差
double 和 float 是浮点数类型,它们在计算机中是以科学计数法的形式存储的,无法精确表示所有的小数,特别是那些不能表示为 2 的负数次方之和的十进制小数。
1 在二进制中是一个无限循环小数,存储时会有微小的舍入误差。
double a = 0.1; double b = 0.2; double sum = a + b; // 预期是 0.3,但实际结果是一个略大于或略小于 0.3 的数 System.out.println(sum == 0.3); // 输出 false! System.out.println(sum); // 可能输出 0.30000000000000004
直接使用 比较两个 double 是否相等,或者使用 >, < 等比较它们的大小,会因为这些微小的误差而得出错误的结果。
正确的比较方法
有几种可靠的方法来比较 double 的大小,你应该根据具体场景选择最合适的一种。

使用误差范围(推荐)
这是最常用且最稳健的方法,我们不直接判断两个数是否相等,而是判断它们的差值是否在一个可以接受的“误差范围”内,这个范围通常被称为“机器 epsilon”或“容差”(tolerance)。
实现原理:
|a - b| < epsilon,我们就认为 a 和 b 是相等的。
如何选择 epsilon?
一个常见的做法是使用 Math.ulp() (Unit in the Last Place) 函数,它返回一个 double 值与其相邻浮点数之间的距离,或者,你可以使用一个非常小的固定值,1e-9 或 1e-15,这取决于你的计算精度要求。
示例代码:

public class DoubleComparison {
// 定义一个很小的误差范围
private static final double EPSILON = 1e-9;
/**
* 比较两个 double 是否在误差范围内相等
*/
public static boolean isEqual(double a, double b) {
// 使用 Math.abs 避免负数影响
// 如果差值的绝对值小于 epsilon,则认为相等
return Math.abs(a - b) < EPSILON;
}
/**
* 比较两个 double 的大小,考虑误差范围
* @return -1 if a < b, 0 if a ≈ b, 1 if a > b
*/
public static int compare(double a, double b) {
double diff = a - b;
if (Math.abs(diff) < EPSILON) {
return 0; // 认为相等
} else if (diff < 0) {
return -1; // a < b
} else {
return 1; // a > b
}
}
public static void main(String[] args) {
double a = 0.1 + 0.2;
double b = 0.3;
System.out.println("a == b: " + (a == b)); // false
System.out.println("isEqual(a, b): " + isEqual(a, b)); // true
double x = 1.000000000001;
double y = 1.000000000002;
System.out.println("x < y: " + (x < y)); // true
System.out.println("compare(x, y): " + compare(x, y)); // -1
double m = 1.0000000000000001; // 这个数可能因为精度问题被当作 1.0
double n = 1.0;
System.out.println("m == n: " + (m == n)); // true
System.out.println("isEqual(m, n): " + isEqual(m, n)); // true
}
}
使用 Double.equals()
Double 是一个包装类,它提供了 equals() 方法来比较两个 Double 对象。
重要区别:
d1.equals(d2):不仅比较double的值,还比较它们的类型,如果其中一个对象是Double,另一个是Float,即使数值相等,也会返回false,它还会处理Double.NaN的情况,NaN.equals(NaN)会返回true。d1 == d2:比较的是两个Double对象的引用地址,而不是它们的值,这通常不是你想要的,除非你是在比较同一个对象。
示例代码:
Double d1 = new Double(0.1 + 0.2); Double d2 = new Double(0.3); // 错误的比较方式,比较的是对象地址 System.out.println(d1 == d2); // false // 正确的比较方式,比较的是值,并处理了 NaN System.out.println(d1.equals(d2)); // false (因为 0.1+0.2 != 0.3) Double d3 = Double.NaN; Double d4 = Double.NaN; System.out.println(d3 == d4); // false System.out.println(d3.equals(d4)); // true (这是 equals 的特殊行为)
虽然 equals() 方法比 安全,但它仍然无法解决浮点数精度问题(如 1+0.2 的例子),它也不是比较 double 数值大小的首选方法。除非你需要严格比较对象类型或处理 NaN,否则优先使用方法一(误差范围)。
使用 BigDecimal(金融或高精度计算场景)
如果计算场景对精度要求极高,例如金融、货币计算等,应该避免使用 double 和 float,转而使用 java.math.BigDecimal。
BigDecimal 可以表示任意精度的十进制数,避免了二进制浮点数的精度问题。
示例代码:
import java.math.BigDecimal;
public class BigDecimalComparison {
public static void main(String[] args) {
// 使用 String 构造函数可以避免 double 构造函数引入的精度问题
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
BigDecimal sum = a.add(b);
BigDecimal c = new BigDecimal("0.3");
// BigDecimal 的比较是精确的
System.out.println("sum.compareTo(c): " + sum.compareTo(c)); // 0 (表示相等)
System.out.println("sum == c: " + (sum.compareTo(c) == 0)); // true
if (sum.compareTo(c) == 0) {
System.out.println("The sum is exactly 0.3");
} else if (sum.compareTo(c) > 0) {
System.out.println("The sum is greater than 0.3");
} else {
System.out.println("The sum is less than 0.3");
}
}
}
注意:
- 永远不要使用
new BigDecimal(double)来构造BigDecimal,因为传入的double本身就已经包含了精度误差。 - 始终使用
new BigDecimal(String)来构造,这样可以确保字符串被精确解析。
总结与最佳实践
| 场景 | 推荐方法 | 原因 |
|---|---|---|
| 一般科学计算、工程计算 | 使用误差范围 (Math.abs(a - b) < EPSILON) |
平衡了性能和精度,是处理浮点数比较问题的通用且最有效的方法。 |
| 金融、货币等高精度计算 | 使用 BigDecimal |
完全避免精度问题,但性能较差,且需要小心构造方式。 |
比较 Double 对象(需处理 NaN) |
使用 Double.equals() |
可以正确处理 NaN,并比较对象类型,但不解决数值精度问题。 |
直接比较 double 原始类型 |
绝对避免 | 几乎总是会导致错误的结果,除非你 100% 确定数值可以精确表示(如整数)。 |
最终建议:
在绝大多数情况下,当你需要比较两个 double 的值是否相等或谁大谁小时,请采用方法一(误差范围),这是 Java 开发中处理浮点数比较的黄金标准。
