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

Java 为什么指定BigDecimal.equals来分别比较值和比例?,java,bigdecimal,Java,Bigdecimal,这不是关于如何比较两个BigDecimal对象的问题-我知道可以使用compareTo而不是equals来进行比较,因为equals被记录为: 与compareTo不同,此方法仅当两个BigDecimal对象的值和比例相等时才认为它们相等(因此,使用此方法进行比较时,2.0不等于2.00) 问题是:equals为什么以这种看似违反直觉的方式指定?也就是说,为什么能够区分2.0和2.00非常重要 这似乎有可能是有原因的,因为指定了compareTo方法的Comparable文档说明: 强烈建议(尽

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

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

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

这似乎有可能是有原因的,因为指定了
compareTo
方法的
Comparable
文档说明:

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


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

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


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

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


如果equals忽略了刻度(例如:如果a.equals(b),您不希望a.add(0.1)equals(b.add(0.1)?)吗?如果数字被四舍五入,它会显示计算的精度,换句话说:

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

换句话说,它链接到。

compareTo方法知道尾随的零不会影响由
BigDecimal
表示的数值,这是
compareTo
唯一关心的方面。相比之下,
equals
方法通常无法知道对象的哪些方面如果程序员可能感兴趣的两个对象在各个方面都是等价的,那么只需要返回
true
。如果
x.equals(y)
是真的,那么
x.toString().equals(y.toString())
会产生false,这是相当令人惊讶的

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

我想一定有充分的理由忽视这一建议

也许不是。我提出了一个简单的解释,
BigDecimal
的设计者只是做了一个糟糕的设计选择

  • 一个好的设计会针对常见的用例进行优化。大多数情况下(>95%),人们希望基于数学等式来比较两个量。对于少数情况下确实关心两个数字在比例和值上相等的情况,可以有一种额外的方法用于此目的
  • 它违背了人们的期望,并且制造了一个很容易落入的陷阱。一个好的API遵循“最少意外的原则”
  • 它打破了通常的Java惯例,
    compariable
    与相等一致
  • 有趣的是,Scala的
    BigDecimal
    类(在后台使用Java的
    BigDecimal
    实现)做出了相反的选择:

    BigDecimal("2.0") == BigDecimal("2.00")     // true
    

    在任何其他答案中尚未考虑的一点是,
    equals
    需要与
    hashCode
    保持一致,并且
    hashCode
    实现的成本需要产生123.0与123.00相同的值(但仍然要合理地区分不同的值)在目前的语义下,
    hashCode
    需要对每32位存储值进行乘法31和加法运算。如果
    hashCode
    需要在不同精度的值之间保持一致,则必须计算no任何值的rmalized形式(昂贵),或者至少,执行类似于计算值的基数-99999999数字根的操作,并根据精度将其乘以mod 99999999。这种方法的内部循环是:

    temp = (temp + (mag[i] & LONG_MASK) * scale_factor[i]) % 999999999;
    
    用64位模运算替换乘31运算——代价要昂贵得多。如果想要一个哈希表,它将数值等效的
    BigDecimal
    值视为等效的,并且大多数键
    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