为什么BigDecimal自然排序与equals不一致?

时间:2013-11-06 17:20:02

标签: java bigdecimal

来自the Javadoc for BigDecimal

  

注意:如果将BigDecimal个对象用作SortedMap中的键或SortedSet中的元素,则应谨慎行事BigDecimal 自然排序 与equals 不一致。

例如,如果您创建HashSet并向其添加new BigDecimal("1.0")new BigDecimal("1.00"),则该集合将包含两个元素(因为这些值具有不同的比例,因此不相等根据{{​​1}}和equals),但是如果你使用hashCode执行相同的操作,则该集合将只包含一个元素,因为当您使用{{1 }}

这种不一致背后是否有任何具体原因?

4 个答案:

答案 0 :(得分:8)

来自BigDecimal的OpenJDK implementation

/**
     * 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(在另一个构造函数中),这就是为什么字符串1.01.00将创建不同的BigDecimals(具有不同的比例)。

来自 Effective Java 作者:Joshua Bloch:

  

compareTo合约的最后一段,这是一个强大的   建议而不是真正的规定,只是说明了   compareTo方法强加的相等测试一般应该返回   与equals方法相同的结果。如果遵守这一规定,   compareTo方法强加的排序被认为是一致的   与平等。如果它被违反,则说明顺序不一致   与平等。 compareTo方法强加订单的类   与equals不一致仍然有效,但排序集合   包含该类的元素可能不遵守一般合同   适当的集合接口(Collection,Set或Map)。这个   是因为这些接口的一般合同是在中定义的   equals方法的术语,但已排序的集合使用相等   由compareTo代替等于的测试。这不是一场灾难   如果发生这种情况,但需要注意的事项。

答案 1 :(得分:3)

在算术精度的上下文中,行为似乎是合理的,其中尾随零是significant figures而1.0不具有与1.00相同的含义。使它们不平等似乎是一个合理的选择。

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

请注意,只要记录,等于和compareTo之间的不一致就不是问题。甚至有时exactly what one needs

答案 2 :(得分:2)

BigDecimal通过两个数字,一个整数和一个比例来工作。整数是“数字”,标度是小数点右边的位数。基本上是10个浮点数。

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

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&amp;&amp; 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进行比较。

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

答案 3 :(得分:2)

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

等于对象=&gt;等值

相等的值= /&gt;平等对象

对象只是某个真实世界值的计算机表示。例如,相同的图片可能以GIF和JPEG格式表示。这非常像BigDecimal,其中相同的值可能具有不同的表示。