当前位置: 首页 > 知识库问答 >
问题:

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

冷宏茂
2023-03-14

来自Javadoc的BigDecimal

注:如果BigDecimal对象用作SortedMap中的键或SortedSet中的元素,则应小心操作,因为BigDecimal的自然顺序与等于的顺序不一致。

例如,如果您创建一个HashSet并向其中添加新的BigDecimal(“1.0”)新的BigDecimal(“1.00”),则该集合将包含两个元素(因为值具有不同的比例,因此根据equalshashCode,它们是不相等的),但是,如果对树集执行相同的操作,则该集将只包含一个元素,因为使用compareTo时,这些值的比较是相等的。

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


共有3个答案

邬博涉
2023-03-14

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

当你说“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

在科学记数法中,这两种方法你都不会做,它应该是1x10^0或者仅仅是1,但bigdecime允许这样做。

compareTo中,忽略刻度,并将其作为普通数字进行计算,1==1。在equals中,比较整数和刻度值,10!=1001!=2。BigDecimal equals方法忽略了我假设的object==这个检查,因为其目的是将每个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的精度()方法时有一个半警告,如果BigDecimal为0,该方法总是返回1。在这种情况下比较

我认为BigDecimal允许尾随零是很奇怪的,但这基本上是必要的。执行“1.1”“1.01”这样的数学运算需要转换,但“1.10”“1.01”不需要。

因此,compareTo将大小数作为数字进行比较,equals将大小数作为大小数进行比较。

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

柳联
2023-03-14

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

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

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

慎懿轩
2023-03-14

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

来自约书亚·布洛赫的有效Java:

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

 类似资料:
  • 休假后回来:)带着问题。我正在阅读比较接口留档从比较文档。我明白,我们使用可比,因为它将为我们提供排序和自然排序。在留档中,它被写成。 强烈建议(尽管不是必需的)自然顺序与equals一致。这是因为没有显式比较器的排序集(和排序映射)在与自然顺序与equals不一致的元素(或键)一起使用时表现“奇怪”。特别是,这样的排序集(或排序映射)违反了用equals方法定义的set(或映射)的一般约定。 “

  • 我可以理解,在大小数中,2.0并不等于2.00,因为2.00实际上使用了更高精度的数字。我很难理解为什么不被认为等于,因为这两个数字实际上使用相同数量的精确数字。依我看,它们只是完全相同数字的不同表示。 我知道,我可以使用和而不是,我只是对这背后的推理感兴趣。有人能帮我理解吗?

  • 问题内容: 实际上,我已经找到了可能的解决方案 当然,可以通过使比较更加健壮的方法来改进它,但是问题是此技术是否可以接受或是否有更好的解决方案? 如果有人知道为什么Java设计人员决定以这种方式实现BigDecimal的equals,那么阅读它会很有趣。 问题答案: 来自BigDecimal的Javadoc 等于 将其与指定的相等性进行比较。与之不同的是,此方法 仅在 两个对象的 值和比例 相等

  • 问题内容: 我已经为此工作了几个月。我只是无法获得(真实的字母数字)结果。令我震惊的是我无法获得自1992年以来的成就。 我正在寻找SQL,VBS或简单的excel或access中的任何解决方案。这是我的数据: 我要查找的顺序是真实的字母数字顺序,如下所示: 库存为7800条记录,因此我在处理能力方面也遇到了一些问题。 任何帮助,将不胜感激。 杰夫 问题答案: 在本机Excel中,您可以添加多个排

  • 问题内容: 什么是自然排序。假设我有一个Employee对象,其名称,年龄和加入日期按什么是自然顺序排序? 问题答案: 自然排序是一种字母数字种类,对人类而言似乎是自然的。 在经典的字母数字排序中,我们将具有以下内容: 1 10 11 12 2 20 21 3 4 5 6 7 如果您使用自然排序,则将为: 根据语言的不同,自然排序有时会忽略大写字母并加重字母(即,所有重音字母都被视为非重音字母)。

  • 问题内容: 请查看下面的代码: 有人可以解释一下为什么输出是 代替 ? 因为在API中它表示优先级队列的元素是根据其自然顺序进行排序的。 问题答案: PriorityQueue基于优先级堆。尽管未对元素进行排序,但此数据结构允许非常快地检索最小元素。将元素添加到PriorityQueue的速度比向基于树的TreeSet快。由于未对元素进行排序,因此如API所述,迭代器“不会以任何特定顺序返回元素”