【Java面试进阶】谈一谈为什么禁止使用BigDecimal的equals方法做等值比较?

BigDecimal在Java中用于高精度计算,其equals方法不仅比较数值,还比较标度,可能导致不准确的等值判断。应当使用compareTo方法进行数值比较。例如,equals在比较1和1.0时可能因标度不同返回不同结果。了解BigDecimal的构造方法对理解标度影响至关重要,如new BigDecimal(1.0)和new BigDecimal(1.0)的标度不同。推荐使用compareTo方法进行数值相等判断。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


BigDecimal是一种java.math包提供的一种可以用来进行精确运算的类型。比如在进行金额表示、金额计算等场景,不能使用double、float等类型,而是要使用对精度支持的更好的BigDecimal,而且其内部自带了很多方法,如加,减,乘,除等运算方法都是可以直接调用的。

当然除了需要用BigDecimal表示数字和进行数字运算以外,代码中还经常需要对于数字进行相等判断。但是需要注意的是:BigDecimal的等值比较应使用compareTo()方法,而不是equals()方法。这是因为BigDecimal是对象,所以不能用’=='来判断两个数字的值是否相等。

使用equals做等值比较会怎么样?

先看一段代码:

package com.seckill.secondkill;

import java.math.BigDecimal;

public class BigDecimalDemo {
    public static void main(String[] args) {
        BigDecimal bigDecimal = new BigDecimal(1);
        BigDecimal bigDecimal1 = new BigDecimal(1);
        System.out.println(bigDecimal.equals(bigDecimal1));

        BigDecimal bigDecimal2 = new BigDecimal(1);
        BigDecimal bigDecimal3 = new BigDecimal(1.0);
        System.out.println(bigDecimal2.equals(bigDecimal3));

        BigDecimal bigDecimal4 = new BigDecimal("1");
        BigDecimal bigDecimal5 = new BigDecimal("1.0");
        System.out.println(bigDecimal4.equals(bigDecimal5));
    }
}

执行结果为:

true
true
false

BigDecimal的equals原理是什么?

通过以上代码发现,在使用BigDecimal的equals方法对1和1.0进行比较的时候,有的时候是true(当使用int、double定义BigDecimal时),有的时候是false(当使用String定义BigDecimal时)。为什么出现这种情况呢?

在BigDecimal的源码中对于equals方法有这样一段注释:

     * 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).

意思就是:equals方法和compareTo并不一样,equals方法会比较两部分内容,分别是值(value)和标度(scale)
BigDecimal的equals源码精度比较
所以示例代码中定义的两个BigDecimal对象(bigDecimal4和bigDecimal5)的标度就不一样,使用equals的结果就是false。

我们再次运行示例代码,并debug到标度比较的位置,如下所示:
debug精度比较
在比较bigDecimal4和bigDecimal5的时候其标度不同,所以结果是false。

为什么标度不同?

为什么bigDecimal2和bigDecimal3的标度是一样的(当使用int、double定义BigDecimal时),而bigDecimal4和bigDecimal5却不一样(使用String定义BigDecimal时)?

这里对标度做简单的介绍:
首先,BigDecimal一共有以下4个构造方法:

BigDecimal(int)
BigDecimal(double)
BigDecimal(long)
BigDecimal(String)

这四种方法,创建出来的BigDecimal的标度是不同的。其中:
最简单的就是BigDecimal(long)BigDecimal(int),因为是整数,所以标度就是0:

    public BigDecimal(int val) {
        this.intCompact = val;
        this.scale = 0;
        this.intVal = null;
    }
    public BigDecimal(long val) {
        this.intCompact = val;
        this.intVal = (val == INFLATED) ? INFLATED_BIGINT : null;
        this.scale = 0;
    }

而对于BigDecimal(double),当使用new BigDecimal(0.1)创建一个BigDecimal的时候,其实创建出来的值并不是正好等于0.1,而是0.1000000000000000055511151231257827021181583404541015625。这是因为double自身表示的只是一个近似值。

因此,无论使用new BigDecimal(0.1)还是new BigDecimal(0.10)定义,它的近似值都是0.1000000000000000055511151231257827021181583404541015625,那么它的标度就是这个数字的位数,即55.
Decimal标度浮点数
对于new BigDecimal(1.0)来说,它本质上是个整数,所以创建出来的数字的标度为0。因为new BigDecimal(1.0)new BigDecimal(1.00)的标度一样,所以在使用equals比较的时候,结果为true。

当我们使用new BigDecimal(”0.1“)创建一个BigDecimal的时候,其实创建出来的值正好就是等于0.1,那么它的标度就是1。如果使用new BigDecimal(”0.10000“),那么创建出来的数字就是0.10000,标度就是5。因为new BigDecimal(”1.0“)new BigDecimal(”1.00“)的标度不一样,所以在equals的时候结果为false。

如何比较BigDecimal?

BigDecimal中提供了compareTo方法,该方法可以只比较两个数字的值,如果两个数字相等,则返回0。

        BigDecimal bigDecimal4 = new BigDecimal("1");
        BigDecimal bigDecimal5 = new BigDecimal("1.000");
        System.out.println(bigDecimal4.compareTo(bigDecimal5)); // 结果为0

compareTo方法的源码如下:

    /**
     * Compares this {@code BigDecimal} with the specified
     * {@code BigDecimal}.  Two {@code BigDecimal} objects that are
     * equal in value but have a different scale (like 2.0 and 2.00)
     * are considered equal by this method.  This method is provided
     * in preference to individual methods for each of the six boolean
     * comparison operators ({@literal <}, ==,
     * {@literal >}, {@literal >=}, !=, {@literal <=}).  The
     * suggested idiom for performing these comparisons is:
     * {@code (x.compareTo(y)} &lt;<i>op</i>&gt; {@code 0)}, where
     * &lt;<i>op</i>&gt; is one of the six comparison operators.
     *
     * @param  val {@code BigDecimal} to which this {@code BigDecimal} is
     *         to be compared.
     * @return -1, 0, or 1 as this {@code BigDecimal} is numerically
     *          less than, equal to, or greater than {@code val}.
     */
    @Override
    public int compareTo(BigDecimal val) {
        // Quick path for equal scale and non-inflated case.
        if (scale == val.scale) {
            long xs = intCompact;
            long ys = val.intCompact;
            if (xs != INFLATED && ys != INFLATED)
                return xs != ys ? ((xs > ys) ? 1 : -1) : 0;
        }
        int xsign = this.signum();
        int ysign = val.signum();
        if (xsign != ysign)
            return (xsign > ysign) ? 1 : -1;
        if (xsign == 0)
            return 0;
        int cmp = compareMagnitude(val);
        return (xsign > 0) ? cmp : -cmp;
    }

总结

  1. BigDecimal是一个非常好用的表示高精度数字的类,其中提供了很多丰富的方法,但是使用equals做比较的时候需要谨慎。
  2. 如果要对两个BigDecimal的数值进行比较的话,可以使用compareTo方法。

原文:《Java开发灵魂17问》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

镰刀韭菜

看在我不断努力的份上,支持我吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值