44

这不是关于如何比较两个BigDecimal对象的问题 - 我知道您可以使用compareTo而不是equals这样做,因为equals记录为:

与 compareTo 不同,此方法仅当两个 BigDecimal 对象的值和比例相等时才认为它们相等(因此,通过此方法进行比较时,2.0 不等于 2.00)。

问题是:为什么equals以这种看似违反直觉的方式来指定?也就是说,为什么能够区分 2.0 和 2.00很重要?

这似乎一定是有原因的,因为Comparable指定compareTo方法的文档指出:

强烈建议(尽管不是必需的)自然排序与 equals 一致

我想一定有充分的理由忽略这个建议。

4

7 回答 7

34

因为在某些情况下,精度指示(即误差范围)可能很重要。

例如,如果您要存储由两个物理传感器进行的测量,那么一个传感器的精度可能是另一个传感器的 10 倍。代表这一事实可能很重要。

于 2012-12-31T13:20:33.807 回答
12

的一般规则equals是两个相等的值应该可以相互替代。也就是说,如果使用一个值执行计算会产生一些结果,那么将一个equals值代入相同的计算应该会得到equals与第一个结果相同的结果。这适用于作为值的对象,例如StringIntegerBigDecimal等。

现在考虑BigDecimal值 2.0 和 2.00。我们知道它们在数值compareTo上相等,并且它们返回 0。但equals返回 false。为什么?

这是它们不可替代的示例:

var a = new BigDecimal("2.0");
var b = new BigDecimal("2.00");
var three = new BigDecimal(3);

a.divide(three, RoundingMode.HALF_UP)
==> 0.7

b.divide(three, RoundingMode.HALF_UP)
==> 0.67

结果显然不相等,因此 的值a不能替代b。所以,a.equals(b)应该是假的。

于 2021-02-12T23:49:28.103 回答
9

在任何其他答案中尚未考虑的一点是,equals它需要与 一致hashCode,并且实现的成本hashCode需要为 123.0 和 123.00 产生相同的值(但仍然可以合理地完成区分不同的值)将比不需要这样做的 hashCode 实现大得多。在目前的语义下,hashCode需要对每 32 位存储的值进行乘以 31 和相加。如果hashCode需要在具有不同精度的值之间保持一致,它要么必须计算任何值的归一化形式(昂贵),要么至少做一些类似计算值的 base-999999999 数字根并将其相乘,mod 999999999,基于精度。这种方法的内部循环是:

temp = (temp + (mag[i] & LONG_MASK) * scale_factor[i]) % 999999999;

用 64 位模数运算代替乘以 31 的运算要贵得多。如果想要一个将数值等价的BigDecimal值视为等价的哈希表,并且可以找到在表中查找的大多数键,那么实现所需结果的有效方法是使用存储值包装器的哈希表,而不是直接存储值。要在表中查找值,首先要查找值本身。如果没有找到,则规范化该值并查找它。如果没有找到,创建一个空包装器并在数字的原始和规范化形式下存储一个条目。

查找不在表中且之前未搜索过的内容将需要昂贵的标准化步骤,但查找已搜索过的内容会快得多。相比之下,如果 HashCode 需要为由于精度不同而以完全不同的方式存储的数字返回等效值,这将使所有哈希表操作变得更慢。

于 2014-07-25T23:25:37.967 回答
6

在数学中,10.0 等于 10.00。在物理学中,10.0m 和 10.00m 可以说是不同的(不同的精度),当谈论 OOP 中的对象时,我肯定会说它们不相等。

如果 equals 忽略了比例,也很容易想到意想不到的功能(例如:如果 a.equals(b),你不会期望 a.add(0.1).equals(b.add(0.1) 吗?)。

于 2012-12-31T13:21:14.503 回答
5

如果数字四舍五入,它会显示计算的精度 - 换句话说:

  • 10.0 可能意味着确切的数字在 9.95 和 10.05 之间
  • 10.00 可能意味着确切的数字在 9.995 和 10.005 之间

换句话说,它与算术精度有关。

于 2012-12-31T13:22:45.830 回答
2

compareTo方法知道尾随零不会影响由 a 表示的数值BigDecimal,这是唯一compareTo关心的方面。相比之下,该equals方法通常无法知道某人关心对象的哪些方面,因此只有true当两个对象在程序员可能感兴趣的所有x.equals(y)方面都等效时才应该返回。如果是真的,那将是相当令人惊讶的为x.toString().equals(y.toString())产生错误。

另一个可能更重要的问题是它BigDecimal本质上结合了 aBigInteger和一个比例因子,这样如果两个数字代表相同的值但具有不同数量的尾随零,则一个将持有一个bigInteger其值是另一个的十倍的幂。如果相等性要求尾数和小数位数都匹配,则hashCode()forBigDecimal可以使用 的哈希码BigInteger。但是,如果两个值可能被认为是“相等的”,即使它们包含不同的BigInteger值,那会使事情变得非常复杂。BigDecimal使用自己的后备存储的类型,而不是BigInteger, 可以通过多种方式实现,以允许数字被快速散列,使得表示相同数字的值比较相等(作为一个简单的例子,一个在每个long值中包含九个十进制数字的版本,并且总是要求小数点位于九个组之间,可以以忽略值为零的尾随组的方式计算哈希码)但是BigDecimal封装 aBigInteger不能这样做。

于 2013-01-21T20:20:20.017 回答
2

我想一定有充分的理由忽略这个建议。

也许不吧。我提出了一个简单的解释,即设计师BigDecimal刚刚做出了错误的设计选择。

  1. 一个好的设计会针对常见的用例进行优化。大多数情况下(>95%),人们希望基于数学等式比较两个量。在少数情况下,您确实关心这两个数字在规模和价值上是否相等,为此目的可能还有另一种方法。
  2. 它违背了人们的期望,并造成了一个很容易陷入的陷阱。一个好的 API 遵循“最小意外原则”。
  3. Comparable它打破了与相等性一致的通常的 Java 约定。

有趣的是,Scala 的BigDecimal类(在底层使用 Java 实现BigDecimal)做出了相反的选择:

BigDecimal("2.0") == BigDecimal("2.00")     // true
于 2014-07-11T08:24:41.043 回答