为什么不能用BigDecimal的equals方法做等值比较?

面试回答

因为 BigDecimal 的 equals 方法和 compareTo 并不一样,equals 方法会比较两部分内容,分别是值(value)和标度(scale),而对于 1.0 和 1 这两个数字,他们的值虽然一样,但是精度是不一样的,所以在使用equals比较的时候会返回 false。

扩展知识

BigDecimal,相信对于很多人来说并不陌生,很多人都知道他的用法,这是一种 java.math 包中提供的一种可以用来进行精确运算的类型。

很多人都知道,在进行金额表示、金额计算等场景,不能使用 double、float 等类型,而是要使用对精度支持更好的 BigDecimal。

所以,很多支付、电商、金融等业务中,BigDecimal 的使用非常频繁。而且不得不说这是一个非常好的类,其内部自带了很多方法,如加、减、乘、除等运算方法都是可以调用的。

关于这个知识点,在最新版的《阿里巴巴Java开发手册》中也有说明:

10.【强制】如上所示 BigDecimal 的等值比较应使用 compareTo() 方法,而不是 equals() 方法。

说明:equals()方法会比较值和精度(1.0与1.00 返回结果为 false),而 compareTo() 则会忽略精度

这背后的思考是什么呢?

BigDecimal 的比较

我在之前的 CodeReview 中,看到过以下这样的低级错误:

        BigDecimal bigDecimal1=new BigDecimal(1);
        BigDecimal bigDecimal2=new BigDecimal("1.0");
        if (bigDecimal1==bigDecimal2){
            // 两个数相等
        }

这种错误,相信聪明的读者一眼就可以看出问题,因为 BigDecimal 是对象,所以不能用 == 来判断两个数字的值是否相等。

以上这种问题,在有一定的经验之后,还是可以避免的,但是聪明的读者,看一下以下这行代码,你觉得他有问题吗?

        BigDecimal bigDecimal1=new BigDecimal(1);
        BigDecimal bigDecimal2=new BigDecimal("1.0");
        if (bigDecimal1.equals(bigDecimal2)){
            // 两个数相等
        }

可以明确的告诉大家,以上这种写法,可能得到的结果和你预想的不一样!

先来做个实验,运行以下代码:

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

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

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

以上代码,输出结果为:

true
true
false

BigDecimal的equals原理

通过以上代码示例,我们发现,在使用 BigDecimal 的equals 方法对1和1.0进行比较的时候,有的时候是 true(当使用 int、double 定义 BigDecimal时),有的时候是 false(当使用 String 定义 BigDecimal 时)。

那么,为什么会出现这样的情况呢?我们先来看一下 BigDecimal 的 equals 方法。

在 BigDecimal 的 JavaDoc 中其实已经解释了其中原因:

Compares this BigDecimal with the specified Object for equality. Unlike compareTo, this method considers two 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)

对应的代码如下:

 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.inflated().equals(xDec.inflated());
    }

所以,我们以上代码定义出来的两个 BigDecimal 对象(bigDecimal5 和 bigDecimal6)的标度是不一样的,所以使用 equals 比较的结果就是 false 了。

到这里,我们大概解释清楚了,之所以 equals 比较 bigDecimal5 和 bigDecimal6 的结果是 false,是因为标度不同。

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

为什么标度不同

这个就涉及到 BigDecimal 的标度问题了,这个问题其实是比较复杂的,由于不是本文的重点,这里面就简单介绍一下吧。大家感兴趣的话,后面单独讲。

首先,BigDecimal 一共有以下 4 个构造方法:

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

以上四个方法,创建出来的 BigDecimal 的标度是不同的。

BigDecimal(long) 和 BigDecimal(int)

首先,最简单的就是 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。

其他的浮点数也同样的道理。对于 new BigDecimal(1.0) 这样形式来说,因为他本质上也是个整数,所以创建出来的数字的标度就是0。

所以,因为 BigDecimal(1.0) 和 BigDecimal(1.00)的标度是一样的,所以使用 equals 方法比较的时候,得到的结果就是 true。

BigDecimal(string)

而对于 BigDecimal(String),当我们使用new BigDecimal("0.1")创建一个 BigDecimal 的时候,其实创建出来的值正好就是等于 0.1 的。那么他的标度也就是1。

如果使用 new BigDecimal("0.10000"),那么创建出来的数就是 0.10000,标度也就是 5。

所以。因为 BigDecimal("1.0")和BigDecimal("1.00")的标度不一样,所以在使用equals方法比较的时候,得到的结果是 false。

如何比较 BigDecimal

前面,我们解释了 BigDecimal 的 equals 方法,其实不只是会比较数字的值,还会对其标度进行比较。

所以,当我们使用 equals 方法判断两个数是否相等的时候,是极其严格的。

那么,如果我们只想判断两个 BigDecimal 的值是否相等,那么该如何判断呢?

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

        BigDecimal bigDecimal5=new BigDecimal("1");
        BigDecimal bigDecimal6=new BigDecimal("1.0");
        System.out.println(bigDecimal5.compareTo(bigDecimal6)); // 输出 0

其源码如下:

    /**
     * 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}.
     */
    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;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

协享科技

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值