25

来自Javadoc 的BigDecimal

BigDecimal注意:如果将对象用作 a 中的键SortedMap或 a 中的元素,则应小心,SortedSet因为BigDecimal自然顺序equals 不一致

例如,如果您创建 aHashSet并向其添加new BigDecimal("1.0")and new BigDecimal("1.00"),则该集合将包含两个元素(因为值具有不同的比例,因此根据equalsand不相等hashCode),但是如果您对 a 执行相同的操作TreeSet,则该集合将只包含一个元素,因为当您使用compareTo.

这种不一致背后有什么具体原因吗?

4

4 回答 4

8

来自 BigDecimal 的OpenJDK 实现

/**
     * Compares this {@code BigDecimal} with the specified
     * {@code Object} for equality.  Unlike {@link
     * #compareTo(BigDecimal) compareTo}, this method considers two
     * {@code BigDecimal} objects equal only if they are equal in
     * value and scale (thus 2.0 is not equal to 2.00 when compared by
     * this method).
     *
     * @param  x {@code Object} to which this {@code BigDecimal} is 
     *         to be compared.
     * @return {@code true} if and only if the specified {@code Object} is a
     *         {@code BigDecimal} whose value and scale are equal to this 
     *         {@code BigDecimal}'s.
     * @see    #compareTo(java.math.BigDecimal)
     * @see    #hashCode
     */
    @Override
    public boolean equals(Object x) {
        if (!(x instanceof BigDecimal))
            return false;
        BigDecimal xDec = (BigDecimal) x;
        if (x == this)
            return true;
    if (scale != xDec.scale)
        return false;
        long s = this.intCompact;
        long xs = xDec.intCompact;
        if (s != INFLATED) {
            if (xs == INFLATED)
                xs = compactValFor(xDec.intVal);
            return xs == s;
        } else if (xs != INFLATED)
            return xs == compactValFor(this.intVal);

        return this.inflate().equals(xDec.inflate());
    }

更多来自实施:

 * <p>Since the same numerical value can have different
 * representations (with different scales), the rules of arithmetic
 * and rounding must specify both the numerical result and the scale
 * used in the result's representation.

这就是为什么要考虑实施的equals原因scale。将字符串作为参数的构造函数实现如下:

    public BigDecimal(String val) {
        this(val.toCharArray(), 0, val.length());
    }

其中第三个参数将用于(在另一个构造函数中) ,scale这就是为什么字符串将创建不同的 BigDecimal(具有不同的比例)。1.01.00

来自Joshua Bloch 的Effective Java :

compareTo 合约的最后一段是一个强烈的建议,而不是真正的规定,它简单地说明了 compareTo 方法施加的相等性测试通常应该返回与 equals 方法相同的结果。如果遵守此规定,则 compareTo 方法强加的顺序称为与 equals 一致。如果违反了,则称该排序与equals不一致。其 compareTo 方法强加的顺序与 equals 不一致的类仍然可以工作,但包含该类元素的排序集合可能不遵守适当集合接口(Collection、Set 或 Map)的一般约定。这是因为这些接口的通用合约是根据 equals 方法定义的,但是已排序的集合使用 compareTo 强加的相等性测试代替 equals。如果发生这种情况,这不是一场灾难,但需要注意。

于 2013-11-06T17:24:38.160 回答
3

在算术精度的背景下,这种行为似乎是合理的,其中尾随零是有效数字,而 1.0 与 1.00 的含义不同。让它们不平等似乎是一个合理的选择。

然而,从比较的角度来看,两者都不大于或小于另一个,并且 Comparable 接口需要一个总顺序(即每个 BigDecimal 必须与任何其他 BigDecimal 可比较)。这里唯一合理的选择是定义一个总顺序,以便 compareTo 方法将两个数字视为相等。

请注意,只要记录在案,equal 和 compareTo 之间的不一致就不是问题。它甚至有时正是人们所需要的。

于 2013-11-07T07:32:19.987 回答
2

BigDecimal 有两个数字,一个整数和一个刻度。整数是“数字”,比例是小数点右边的位数。基本上是一个以 10 为基数的浮点数。

当您说这些在 BigDecimal 表示法中在技术上是不同的值时"1.0""1.00"

1.0
      integer: 10
        scale: 1
    precision: 2
             = 10 x 10 ^ -1

1.00
      integer: 100
        scale: 2
    precision: 3
             = 100 x 10 ^ -2

在科学记数法中,您不会这样做,应该是1 x 10 ^ 0或只是1,但 BigDecimal 允许这样做。

compareTo比例尺中被忽略,它们被评估为普通数字,1 == 1。在equals整数和比例值进行比较,10 != 1001 != 2。BigDecimal equals 方法忽略了object == this我假设的检查,因为其目的是将每个 BigDecimal 都视为一种数字,而不是对象。

我会把它比作:

// same number, different types
float floatOne = 1.0f;
double doubleOne = 1.0;

// true: 1 == 1
System.out.println( (double)floatOne == doubleOne );

// also compare a float to a double
Float boxFloat = floatOne;
Double boxDouble = doubleOne;

// false: one is 32-bit and the other is 64-bit
System.out.println( boxInt.equals(boxDouble) );

// BigDecimal should behave essentially the same way
BigDecimal bdOne1 = new BigDecimal("1.0");
BigDecimal bdOne2 = new BigDecimal("1.00");

// true: 1 == 1
System.out.println( bdOne1.compareTo(bdOne2) );

// false: 10 != 100 and 1 != 2 ensuring 2 digits != 3 digits
System.out.println( bdOne1.equals(bdOne2) );

因为 BigDecimal 允许特定的“精度”,所以比较整数和小数位数与比较数字和精度大致相同。

尽管在谈论 BigDecimal 的precision() 方法时有一个半警告,如果BigDecimal 为0,该方法总是返回1。在这种情况下,compareTo && precision 评估为true,equals 评估为false。但0 * 10 ^ -1不应该相等0 * 10 ^ -2,因为前者是 2 位数字0.0,后者是 3 位数字0.00。equals 方法是比较值和位数。

我想 BigDecimal 允许尾随零很奇怪,但这基本上是必要的。像这样进行数学运算"1.1" + "1.01"需要转换,但"1.10" + "1.01"不需要。

因此,compareTo将 BigDecimals 与数字进行比较,equals并将 BigDecimals 与 BigDecimals 进行比较。

如果不需要比较,请在无关紧要的地方使用列表或数组。HashSet 和 TreeSet 当然是专门为保存独特元素而设计的。

于 2013-11-06T18:40:24.060 回答
2

答案很简短。equals() 方法比较对象,而 compareTo() 比较值。在 BigDecimal 的情况下,不同的对象可以表示相同的值。这就是为什么 equals() 可能返回 false,而 compareTo() 返回 0。

相等的对象 => 相等的值

相等的值 =/> 相等的对象

对象只是一些现实世界价值的计算机表示。例如,相同的图片可能以 GIF 和 JPEG 格式表示。这很像 BigDecimal,其中相同的值可能有不同的表示。

于 2013-11-07T06:26:11.550 回答