前言
当我们进行浮点数运算的时候会产生精度丢失的问题
float a = 2.0f - 1.9f;
float b = 1.8f - 1.7f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999905
System.out.println(a == b);// false
首先来看一下,为什么会有精度丢失 ?
是因为在计算机中,数据是以二进制的形式表示,但不是所有的十进制数字都可以由二进制数字进行表示,例如0.1这个数字在二进制中是一个无限循环的小数,所以像这样的数就会精度丢失。
我们可以使用BigDecimal来避免精度丢失。
BigDecimal原理
public class BigDecimal {
//值的绝对long型表示
private final transient long intCompact;
//值的小数点后的位数
private final int scale;
private final BigInteger intVal;
//值的有效位数,不包含正负符号
private transient int precision;
private transient String stringCache;
//加、减、乘、除、绝对值
public BigDecimal add(BigDecimal augend) {}
public BigDecimal subtract(BigDecimal subtrahend) {}
public BigDecimal multiply(BigDecimal multiplicand) {}
public BigDecimal divide(BigDecimal divisor) {}
public BigDecimal abs() {}
}
上面是BigDecimal类,里面有两个重要的参数
- intCompact
- scale
因为十进制转换二进制会出现问题,所以我们将数字扩大N倍,让其在整数的维度上进行计算,二intCompact用来保存这个数字。
而scale用来表示小数点后的位数,这样在intCompact和scale的作用下,就可以保证计算不会受到二进制的限制。
举个例子,如果我们计算0.1和0.2,BigDecimal会将他们转换为整数1和2,然后设置scale为1,之后在进行计算。
上面两个参数分别是BigDecimal的两个原理,分别为精确数值表示和扩大为整数运算。
还有两个原理分别是
BigDecimal提供了一系列方法来进行数学运算,例如add,subtract,multiply,divide。
有避免精度丢失的构造方法。
BigDecimal b = new BigDecimal("0.1")//里面是String类型
//不可以是double。这样在入参的时候就会产生精度丢失
BigDecimal等值比较问题
BigDecimal等值比较应使用compareTo()方法,而不是equals()方法。
public boolean equals(Object x) {
if (!(x instanceof BigDecimal)) {
return false;
}
BigDecimal xDec = (BigDecimal)x;
if (x == this) {
return true;
} else if (this.scale != xDec.scale) {
// 如果标度值不同,则返回false。
// 所以,1.0和 1.00不相等(前者标度值为1,后者标度值为2)
return false;
} else {
// 进行值比较
long s = this.intCompact;
long xs = xDec.intCompact;
if (s != -9223372036854775808L) {
if (xs == -9223372036854775808L) {
xs = compactValFor(xDec.intVal);
}
return xs == s;
} else if (xs != -9223372036854775808L) {
return xs == compactValFor(this.intVal);
} else {
return this.inflated().equals(xDec.inflated());
}
}
}
public int compareTo(BigDecimal val) {
if (this.scale == val.scale) {
long xs = this.intCompact;
long ys = val.intCompact;
if (xs != -9223372036854775808L && ys != -9223372036854775808L) {
return xs != ys ? (xs > ys ? 1 : -1) : 0;
}
}
// 如果标度值不同,会将标尺值对齐后再比较
// 所以,不存在1.0和 1.00不相等的情况。
int xsign = this.signum();
int ysign = val.signum();
if (xsign != ysign) {
return xsign > ysign ? 1 : -1;
} else if (xsign == 0) {
return 0;
} else {
// compareMagnitude会对齐标度值,这里不展开,有兴趣的可以阅读相关源码
int cmp = this.compareMagnitude(val);
return xsign > 0 ? cmp : -cmp;
}
}
分析源码可知,在使用equals()方法比较两个BigDecimal时,如果标度值(scale)不同,则返回false。这对于标度值不同,值相同的场景不适用。
而compareTo()则没有这个问题。所以使用BigDecimal进行等值比较时,应使用compareTo()方法,而不是equals()方法。
总结
本文较为清晰的说明了BigDecimal的原理和等值比较问题,其中原理部分解释为什么BigDecimal可以解决精度丢失问题,等值比较部分,也放出源码,从源头上解决了equals和compareTo()的不同的原因。