为什么指定BigDecimal.equals分别比较值和比例?

bac*_*car 33 java bigdecimal

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

与compareTo不同,此方法仅考虑两个BigDecimal对象的值和比例相等(因此通过此方法比较时2.0不等于2.00).

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

似乎必须有这样的理由,因为Comparable指定compareTo方法的文档指出:

强烈建议(尽管不要求)自然排序与equals一致

我想必须有一个很好的理由忽略这个建议.

Oli*_*rth 25

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

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

  • 根据我的经验,您希望`equals()`捕获精确语义差异的情况远比直观案例少得多.最重要的是,直观的例子意味着`BigDecimal`的`compareTo()`与`equals()`一致.在我看来,太阳在这里犯了一个错误. (23认同)
  • @bacar是一个实现,其特征是像`boolean equalsWithPrecision(BigDecimal other)`这样的方法将允许两个函数,*和*保持一致. (9认同)
  • 它似乎[打破设置和地图使用](http://stackoverflow.com/questions/20091723/how-do-i-check-if-a-bigdecimal-is-in-a-set-or-map-在-规模无关-路). (4认同)
  • @bowmore,这也是我的猜测,但经验各不相同.纯粹主义者可以说他们应该提供2个类 - 一个不适合排序的类(没有`compareTo`),它将精确度作为对象的可见部分; 第二个类实现`Comparable`,`compareTo`与`equals`一致,将scale和value视为一个整体.然而,提供两者可能看起来相当臃肿/不实用并创造而不是化解混淆 - Sun通过提供不一致的`compareTo`和`equals`(并且让我们中的许多人一路惊讶)来允许这两种功能. (3认同)
  • @GeoffreyDeSmet:这种用法是否“被破坏”取决于该集的预期目的。如果创建一个集合的目的是允许对等效但不同的实例的引用替换为对单个实例的引用,那么“equals”的行为是完美的;我认为“等于”的定义与用法不一致有些危险。 (3认同)

Stu*_*rks 8

一般规则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
Run Code Online (Sandbox Code Playgroud)

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

  • @Eugene 这个例子非常好,我们决定将其放入 javadoc 中:https://github.com/openjdk/jdk/commit/a1181852(它应该出现在 JDK 17 build 13 中)。 (2认同)
  • @霍尔格正确。[JDK-8223933](https://bugs.openjdk.java.net/browse/JDK-8223933)。 (2认同)

sup*_*cat 7

在任何其他答案中尚未考虑的一点equals是,要求与123.00相同,并且为123.0产生相同值所需hashCodehashCode实施成本(但仍然做得合理区分不同的值)将远远大于不需要这样做的hashCode实现.在当前语义下,hashCode需要乘以31并为每个32位的存储值添加.如果hashCode要求在具有不同精度的值之间保持一致,则要么必须计算任何值的标准化形式(昂贵),要么至少执行类似计算值的基数-9999999999数字根并将其相乘, mod 999999999,基于精度.这种方法的内部循环是:

temp = (temp + (mag[i] & LONG_MASK) * scale_factor[i]) % 999999999;
Run Code Online (Sandbox Code Playgroud)

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

寻找不在表中并且之前未被搜索过的东西需要昂贵的标准化步骤,但寻找已经搜索过的东西会快得多.相比之下,如果HashCode需要返回数字的等价值,由于精度不同,它们的存储方式完全不同,这会使所有哈希表操作都慢得多.


Ale*_*øld 6

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

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

  • 好的。我知道有时用户可能想要考虑精度,但我仍然不明白您对意外功能的看法。如果他们选择让 2.0 等于 2.00,我不确定您添加 0.1 的示例在哪里导致问题。 (6认同)
  • 是的,我希望如此,但我不明白你的意思;我并不是建议它忽略规模;我建议它将价值和规模视为*整体*,就像 `compareTo` 所做的那样。 (2认同)

ass*_*ias 5

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

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

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