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)} <<i>op</i>> {@code 0)}, where
* <<i>op</i>> 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