好久也没有写点什么了,虽然一直想根据某个切入点写一篇比较完整的文章,但是总是没有抽空写。正好有点空闲,姑且就先写个最近遇到的小问题吧,也是做个备忘。
背景
正好之前有写过一个小工具,用来比对对象之间的差异 对比小工具 ,最近在项目的使用过程中,突然发现对于处理BigDecimal的时候存在一个问题,就是对于 0 和 0.00 认为他们是不一样的。
虽然主要的问题还是我们在从数据库读取值以及设置默认值的时候,对于精度(scale)的处理不一致导致的,不过分析了BigDecimal的equals方法,也觉得很有意思。
问题原因
其实在BigDecimal的实现类里面注释已经说的很清楚了,不过如果不注意看的话,还是很容易忽略掉这个细节的。
具体来说就是BigDecimal的Equals方法,在对比的时候是要比较scale的,也就是说如果scale不相等,也是会返回false的。比如0和0.00,因为两者的scale是不同的,所以结果自然是false。
不得不说,确实从Decimal的设计原理上来说,确实这个考虑也是合理的,因此在使用的时候我们还是需要多多注意的。
/**
* 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());
}
那么对于这种本身就需要忽略scale的对比怎么办?其实BigDecimal类也提供了相关的compare方法,而且这个方法的设计也和comparable接口的实现也很相似,所以使用起来也挺舒服的。
/**
* 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;
}
解决方案
由于很多场景下我们都是在算费等场景下使用BigDecimal,所以在比对BigDecimal的时候,尽量使用compare方法(忽略scale)来比较,而非equals方法,可能更符合我们的需求。
p.s. 最后既然说到了scale的问题,也顺便提一下关于precision的一个小例子,相信如果明白了为什么需要使用BigDecimal而非Double的小伙伴们一定会会心一笑的。
double sum = 0.0d;
for( int i=0; i<100; i++ ){
sum = sum + 0.01;
}
// 结果是 true 还是 false ?
System.out.println("result is " + (sum==1.00d) );
网友评论