【问题标题】:Why BigDecimal("5.50") not equals to BigDecimal("5.5") and how to work around this issue?为什么 BigDecimal("5.50") 不等于 BigDecimal("5.5") 以及如何解决这个问题?
【发布时间】:2010-10-05 18:19:51
【问题描述】:

其实我已经找到了可能的解决方案

//returns true
new BigDecimal("5.50").doubleValue() == new BigDecimal("5.5").doubleValue()

当然,可以使用Math.abs (v1 - v2) < EPS 之类的方法进行改进,以使比较更加稳健,但问题是这种技术是否可以接受,还是有更好的解决方案?

如果有人知道为什么 java 设计者决定以这种方式实现 BigDecimal 的 equals,那么阅读起来会很有趣。

【问题讨论】:

  • 如果保证您的 BigDecimal 对象始终可以用双精度表示,那么您无论如何都不应该使用 BigDecimal。如果不是,则此方法将失败。
  • 错误的解决方案。如果双打适合您的程序,请使用双打。如果 BigDecimals 合适,请使用 BigDecimals。来回转换几乎没有用处。
  • @DJClayworth:你在哪里看到“已编辑”标签?
  • 你是对的,我没有。我认为它已被编辑,因为您自己回答了它。我很抱歉。
  • 由于没有人回答您关于 为什么 BigDecimal.equals 以这种方式指定的评论,我已经明确询问:Why is BigDecimal.equals specified to compare both value and scale individually?

标签: java equals bigdecimal


【解决方案1】:

来自 BigDecimal 的 javadoc

等于

public boolean equals(Object x)

比较这个BigDecimal 和指定的Object 是否相等。与compareTo 不同,此方法认为两个BigDecimal 对象仅当它们在值和规模上相等时才相等(因此,在此方法比较时,2.0 不等于 2.00)

只需使用compareTo() == 0

【讨论】:

  • 因为equals的实现不仅基于值,还基于尺度,这样就避免了new BigDecimal("5.01").equals(new BigDecimal("5.0")) == falsenew BigDecimal("5.0").equals(new BigDecimal("5.01")) == true。这一切都是因为equals() is symmetric
  • 你不需要放弃对称来实现equals作为数值比较,所以对称不是原因。我在这里明确提出了“为什么”的问题:Why is BigDecimal.equals specified to compare both value and scale individually?
  • @bacar:除此之外,x.equals(y) 返回 true 而 x.toString().equals(y.toString()) 返回 false 会令人惊讶。
  • @supercat 我认为您不必期望它们返回相同的值; toString 没有要求与equals 一致。考虑java.util.Set 的实现——它们有一个严格指定的equals 合约,但toString 可以按任何顺序返回项目。
  • @supercat 有关 JDK 的具体示例,请考虑 Set<String> s1 = new LinkedHashSet<String>(); s1.add("foo"); s1.add("bar"); Set<String> s2 = new LinkedHashSet<String>(); s2.add("bar"); s2.add("foo");s1s2 有不同的字符串表示,但比较相等。
【解决方案2】:

比较忽略尾随零的最简单表达式是从 Java 1.5 开始:

bd1.stripTrailingZeros().equals(bd2.stripTrailingZeros())

【讨论】:

    【解决方案3】:

    一般使用== 比较双精度数seems like a bad idea

    你可以在你比较的数字上调用 setScale 来做同样的事情:

    new BigDecimal ("5.50").setScale(2).equals(new BigDecimal("5.5").setScale (2))
    

    您要将比例设置为两者中的较大者:

    BigDecimal a1 = new BigDecimal("5.051");
    BigDecimal b1 = new BigDecimal("5.05");
    // wow, this is awkward in Java
    int maxScale = Collections.max(new ArrayList() {{ a1.scale(), b1.scale()}});
    System.out.println(
      a1.setScale(maxScale).equals(b1.setScale(maxScale)) 
      ? "are equal" 
      : "are different" );
    

    不过,使用compareTo() == 0 是最好的答案。在我上面的方法中,其中一个数字的比例增加可能是 compareMagnitude 方法文档在说时提到的“不必要的膨胀”:

    /**
     * Version of compareTo that ignores sign.
     */
    private int compareMagnitude(BigDecimal val) {
        // Match scales, avoid unnecessary inflation
        long ys = val.intCompact;
        long xs = this.intCompact;
    

    当然compareTo 更容易使用,因为它已经为您实现了。

    【讨论】:

    • 只有在你想考虑 "5.051" == "5.05" 时才这样做,因为 setScale(2) 将删除那个额外的数字,而 .compareTo(other) 将比较值而不考虑规模
    • Gareth, setScale 没有舍入模式参数不会删除多余的数字,而是会抛出 ArithmeticException。所以这只有在额外的数字全为零时才有效。
    • @Gareth:感谢您的反馈。在我很久以前写这篇文章的时候,我还没有进入 cmets 和通知,所以我错过了这个。终于注意到了这一点,并根据您的评论进行了更新。
    • Max.max(a1.scale(), b1.scale()) 而不是 Collections.max(new ArrayList() {{ a1.scale(), b1.scale()}})
    猜你喜欢
    • 1970-01-01
    • 2021-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-03
    • 2013-03-05
    相关资源
    最近更新 更多