BigDecimal的equals方法做等值比较问题

BigDecimal是java.math包中提供的一种可以用来进行精确运算的类型。在进行金额表示 、金额计算等场景,不能使用double和float等类型,而是使用BigDecimal。

BigDecimal表示数字和进行数字运算之外,代码中还会对BigDecimal进行相等判断。

最新阿里Java开发手册中指出

禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象。
说明:
BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。
如:
BigDecimal b = new BigDecimal(0.1F);实际的存值为:0.10000000149
正例:
优先推荐入参为String的构造方法,或使用BigDecimal的valueOf方法,此方法内部其实执行了Double的toString,而Double的toString按double的实际表达的精度对尾数进行了截断。

BigDecimal b1 = new BigDecimal(0.2)BigDecimal b2 = BigDecimal.valueOf(0.2);

下面进行出现这样原因的分析

在经验不足时,我们会见到这样的低级错误

//判断两个数相等
if(bigDecimal1 == bigDecimal2){
	//.......
}

因为BigDecimal 是对象,所以不能用==来判断这两个数字的值是否相等。

equals作判断的问题

//判断两个数相等
if(bigDecimal1.equals(bigDecimal2)){
	//......
}

以上的结果会和我们预想的结果有时候不一样。

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

BigDecimal  bigDecimal3 = new BigDecimal(2);
BigDecimal  bigDecimal4 = new BigDecimal(2.0);
System.out.println(bigDecimal3.equals(bigDecimal4));

BigDecimal  bigDecimal5 = new BigDecimal("2");
BigDecimal  bigDecimal6 = new BigDecimal("2.0");
System.out.println(bigDecimal5.equals(bigDecimal6));

以上代码输出结果为:

true

true

false

BigDecimal的equals原理

通过上面实验,使用BigDecimal的equals方法比较2和2.0时候,有时候是true(当使用int、double定义BigDecimal时),有时候是false(当使用String定义BigDecimal时)。

BigDecimal源码:

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

在BigDecimal中的equals方法被重写,equals方法和compareTo并不一样,equals方法会比较两部分内容,分别是值(value)和标度(scale)。
上面定义出来的两个BigDecimal对象,BigDecimal5和BigDecimal6的标度是不一样的。所以equals比较的结果为false。

标度不同原因

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

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

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

BigDecimal(int)和BigDecimal(long)
BigDecimal(int)和BigDecimal(long),因为是整数,所以标度就是0

BigDecimal(double)
对于BigDecimal(double),当我们使用new BigDecimal(0.1)创建一个BigDecimal的时候,其实创建出来的值并不是0.1,而是0.10000000000000000555111…的数。因为double自身表示的只是一个近似值。

那么不论我们使用new BigDecimal(0.1)还是new BigDecimal(0.10)定义,他的近似值都不变。

BigDecimal(1.0)和BigDecimal(1.00)的标度是一样的;

BigDecimal(String)
对于BigDecimal(double),当我们使用new BigDecimal(“0.1”)创建一个BigDecimal的时候,创建出来的值就等于0.1.那么他的标度也是1.

如果new BigDecimal(“0.10000”),那么创建出来的数就是0.10000,标度也就是5.

所以BigDecimal(“3.0”)和BigDecimal(“3.00”)的标度不一样,所以使用equal方法比较的时候,得到的结果是false。

BigDecimal做比较

当我们判断BigDecimal是否相等,用compareTo方法,如果两个数相等,则返回0.

BigDecimal  bigDecimal5 = new BigDecimal("2");
BigDecimal  bigDecimal6 = new BigDecimal("2.0000000");
System.out.println(bigDecimal5.compareTo(bigDecimal6));

输出结果:

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}.
     */
    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;
    }

共同探讨学习技术创建技术氛围Day9884125


版权声明:本文为m0_45226909原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。