Java 中比较 BigDecimal 的陷阱

在 Java 中使用浮点数时,开发人员经常求助于 BigDecimal 类进行精确计算。但是,如果使用不当,BigDecimal 中的 equals() 方法的行为可能会导致意外结果。在这篇博文中,我将说明比较 BigDecimals 可能比您想象的要难。

理解BigDecimal
BigDecimal 是 Java 中的一个类,用于表示任意精度的浮点数。它由一个非标度值 (BigInteger) 和一个标度 (int) 组成,后者表示以 10 为基数的指数。例如,BigDecimal 值 0.1234 的非标度值为 1,234,标度为 4(0.1234 = 1234 × 10^-4)。

陷阱
开发人员常犯的一个错误是假设 BigDecimal 中的 equals() 方法会比较数值。然而,equals() 实际上会比较非标度值和标度,而不是数值等价性。当比较具有不同标度的 BigDecimal 时,这可能会导致令人惊讶的结果。

BigDecimal zero1 = new BigDecimal("0");
BigDecimal zero2 = new BigDecimal(
"0.0");
System.out.println(zero1.scale());
// Output: 0
System.out.println(zero2.scale());
// Output: 1
System.out.println(zero1.equals(zero2));
// Output: false

在本例中,我们向 BigDecimal 构造函数传递了一个 Java 字符串,以创建一个 BigDecimal 对象,但尽管 zero1 和 zero2 都表示值 0,它们的刻度却不同。因此,equals() 方法返回 false,表明它们不相等。

解决方法
要避免这个陷阱,并根据 BigDecimals 的数值对其进行比较,您有几个选择:

1、使用 compareTo() 方法:
compareTo() 方法比较 BigDecimals 的数值,而不考虑它们的比例。如果数值相等,则返回 0。下面是一个示例:

BigDecimal value1 = new BigDecimal("1.23");
BigDecimal value2 = new BigDecimal(
"1.230");
System.out.println(value1.compareTo(value2));
// Output: 0

2、使用 stripTrailingZeros() 将 BigDecimals 归一化:
如果仍然需要使用 equals() 方法,可以使用 stripTrailingZeros() 方法去除尾数零,从而将 BigDecimals 归一化。该方法将创建一个新的 BigDecimal 对象,并在不降低精度的前提下尽可能减小刻度。下面是一个示例:

BigDecimal zero1 = new BigDecimal("0").stripTrailingZeros();
BigDecimal zero2 = new BigDecimal(
"0.0").stripTrailingZeros();
System.out.println(zero1.scale());
// Output: 0
System.out.println(zero2.scale());
// Output: 0
System.out.println(zero1.equals(zero2));
// Output: true

去掉尾部的零后,zero1 和 zero2 的刻度相同,equals() 方法可以正确地将它们识别为相等。

结论
由于 equals() 方法的行为,在 Java 中比较 BigDecimals 可能很棘手。通过了解这些陷阱并使用适当的比较技术(如 compareTo() 或对 BigDecimals 进行规范化处理),您可以确保基于数值的比较准确无误。在使用 BigDecimals 时,请牢记这些注意事项,以避免在 Java 程序中出现意外结果。