BigDecimal是专门为弥补浮点数无法精确计算的遗憾而设计的类,并且提供了加减乘除的常用数学算法。特别是与数据库Decimal类型的字段映射时,BigDecimal是最佳的解决方案。
浮点数计算的缺陷
十进制的浮点数转化为二进制小数,在计算机中进行表达存储,采用的方法为“除2取整,顺序排列”。由于计算机浮点数的存储规则限制,因此计算机中的浮点数有可能不准确,只能无限接近于准确值,而不能精确。
public class BigDecimalTest {
public static void main(String[] args) {
System.out.println(10.00 - 9.60);
}
}
浮点数计算误差解决方式
由于浮点数的存储规则限制,浮点数的计算存在着误差,而在金融会计领域,预算和核算需要精确的结果,这样一来就不能使用直接截断进行结果输出了。对于这种情况有两种解决方式:
使用整形
- 使用整型 把参与运算的值扩大100倍,并转变为整型,然后在计算完成后再缩小100倍,这样处理的好处就是简单、准确。在非金融行业(比如零售业)应用较多。
- 使用BigDecimal 在一些Java面试时或是进行财务类型的开发过程中,经常使用到BigDecimal类作为数据运算的载体,因为这样就可以避免因为计算机浮点数误差而带来的种种问题。特别是与数据库Decimal类型的字段映射时,BigDecimal是最佳的解决方案。
BigDecimal使用方法
构造方法
double d = 10.0 - 9.6;
BigDecimal bigDecimal = new BigDecimal(d); // 不允许使用
BigDecimal bigDecimal1 = BigDecimal.valueOf(d); //valueof
BigDecimal bigDecimal2 = new BigDecimal("9.6"); //推荐使用
System.out.println(bigDecimal + "\n" + bigDecimal1 + "\n" + bigDecimal2);
[分析]
- 不推荐使用BigDecimal(double val)构造器,因为使用该构造器时有一定的不可预知性,当程序使用new BigDecimal(0.1)创建一个BigDecimal对象时,它的值并不是0.1,实际上是一个近似0.1的数。
- 如果必须使用double浮点数作为BigDecimal构造器的参数时,不要使用double作为参数,而应该通过BigDecimal.valueOf(double value)静态方法来创建对象。
- 建议优先使用基于String的构造器,使用BigDecimal(String val)构造器时可以预知的,写入new BigDecimal(“0.1”)将创建一个恰好等于0.1的BigDecimal。
类成员方法
加减乘除
public BigDecimal add(BigDecimal augend):加
public BigDecimal subtract(BigDecimal subtrahend):减
public BigDecimal multiply(BigDecimal multiplicand):乘
public BigDecimal divide(BigDecimal divisor):除
public BigDecimal divide(BigDecimal divisor,int scale, int roundingMode):商,几位小数,舍取模式
BigDecimal a = new BigDecimal("10.00");
BigDecimal b = new BigDecimal("9.60");
System.out.println("a + b = " + a.add(b));
System.out.println("a - b = " + a.subtract(b));
System.out.println("a * b = " + a.multiply(b));
System.out.println("a / b = " + a.divide(b,2,BigDecimal.ROUND_DOWN));
比较方法
equals()代码
@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) //比较精度,不等返回false
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());
}
compareTo()代码
@Override
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;
}
== 分析
== 判断的是类对象的引用是否相等,而不是值比较,即不同对象一定不相等。
测试代码
double A = 0.1;
double B = 0.10;
System.out.println(BigDecimal.valueOf(A).equals(BigDecimal.valueOf(B))); // valueof构造,equals
System.out.println(BigDecimal.valueOf(A).compareTo(BigDecimal.valueOf(B))); // valueof构造,compareTo
System.out.println(new BigDecimal("0.1").equals(new BigDecimal("0.10"))); //String构造,equals
System.out.println(new BigDecimal("0.1").compareTo(new BigDecimal("0.10"))); //String构造,compareTo
compareTo返回值为0,表示true;返回值为1,
[注意]
- BigInteger和BigDecimal都是不可变(immutable)的,在进行每一步运算时,都会产生一个新的对象,由于创建对象会引起开销,它们不适合于大量的数学计算,应尽量用long,float,double等基本类型做科学计算或者工程计算。设计BigInteger和BigDecimal的目的是用来精确地表示大整数和小数,使用于在商业计算中使用。
- 应该避免使用double构造BigDecimal,因为:有些数字用double根本无法精确表示,传给BigDecimal构造方法时就已经不精确了。
- equals()方法认为0.1和0.1是相等的,返回true,而认为0.10和0.1是不等的,结果返回false。 方法compareTo()则认为0.1与0.1相等,0.10与0.1也相等。即:equals方法会比较值和精确度,而compareTo则会忽略精度。所以在从数值上比较两个BigDecimal值时,应该使用compareTo()而不是 equals()。 尤其是处理支付金额校验的时候,用BigDecimal的equals方法来比较两个金额是否相等,可能导致金额比较出现错误(比如3.0与3.00的比较等)。
- 另外还有一些情形,任意精度的小数运算仍不能表示精确结果。例如,1除以9会产生无限循环的小数 .111111…。 出于这个原因,在进行除法运算时,BigDecimal可以让您显式地控制舍入。
TODO
- BigDecimal 除法等详细使用方法
- BigDecimal 底层源码及实现原理分析