BigDecimal 由任意精度的整数非标度值和32位的整数标度(scale)组成。BigDecimal表示的数值是(unscaledValue × 10-scale)
BigDecimal 的存在原因
任何十进制整数都可以精确转换成一个二进制整数,但任何一个十进制小数却不一定能精确转换为一个二进制小数。例如 0.7转为二进制是 0.1011001100110011...
用有限的二进制位无法准确表示某些小数,当我们在计算时就会损失精度。
Java 里用 float 和 double 来进行某些小数之间的计算可能会损失精度。例如
double d1 = 2.9;
double d2 = 2.5;
System.out.println(d1-d2);
//结果是0.3999999999999999
这在商业计算领域无疑是不被允许的,怎么解决这个问题呢?
BigDecimal 就是用来进行这种精确计算的。
BigDecimal 的实现原理
任何十进制整数都可以精确转换为有限位的二进制整数。
通过把小数扩大到整数的维度上进行计算即可保留相应的精度信息。
下面来看下 BigDecimal 类的部分源码。
- intVal 字段是存放整数非标度值(放大了的整数)。当该 BigDecimal 的有效数的绝对值小于或等于Long.MAX_VALUE ,此字段为空。
- scale 字段是标度(值的小数点后的位数)
- precision 字段是此 BigDecimal 中的十进制位数(即整数非标度值的数字位数),如果位数未知(后备信息),则为 0。 如果非零,则保证该值正确。
- intCompact 字段也是用来存整数的非标度值。如果该 BigDecimal 的有效数的绝对值小于或等于Long.MAX_VALUE ,则该值可以紧凑地存储在该字段中并用于计算。
把 2.9 的放大成 29 作为非标度值,小数点后只有一位,故1 作为标度,这就是 BigDecimal。
BigDecimal 的构造
常用的两个构造器是
- BigDecimal(String)
- BigDecimal(double)
例如
BigDecimal b1 = new BigDecimal("2.9");
BigDecimal b2 = new BigDecimal(2.9);
更推荐使用通过字符串去构造 BigDecimal 。看以下运行结果
参数类型为 double 的构造方法的结果有一定的不可预知性。利用 double 作为参数的构造函数,无法精确构造一个 BigDecimal 对象 ,而利用 String 对象作为参数传入的构造函数能精确的构造出一个 BigDecimal 对象。
也可以通过以下方法构建
BigDeciamal b3 = BigDecimal.valueOf(2.9);
从其源码我们可以看出它本质是用参数类型为 String 的构造器创建 BigDecimal。
BigDecimal 的运算
BigDecimal 是不可变(immutable)的,它的所有操作都会生成一个新的对象
BigDecimal b1 = new BigDecimal("2.9");
BigDecimal b2 = BigDecimal.valueOf(2.5);
//加法 b1+b2
b1.add(b2);
//减法 b1-b2
b1.subtract(b2);
//乘法 b1*b2
b1.multiply(b2);
//除法 b1/b2
b1.divide(b2);
【注意】除法运算时,结果不能整除时会报 java.lang.ArithmeticException;解决方法如下
//divide(BigDecimal divisor, int scale, int roundingMode);
//第二个参数为保留多少位小数,第三个参数为舍入模式
b1.divide(b2,10,BigDecimal.ROUND_HALF_UP)
舍入模式 | 解释 |
---|---|
ROUND_UP | 向远离零的方向舍入。舍弃非零部分,并将非零舍弃部分相邻的一位数字加一。 |
ROUND_DOWN | 向零方向舍入。舍弃非零部分,同时不会将非零舍弃部分相邻的一位数字加一,采取截取行为 |
ROUND_CEILING | 向正无穷方向舍入。如果为正数,舍入结果同ROUND_UP一致;如果为负数,舍入结果同ROUND_DOWN一致。注意:此模式不会减少数值大小。 |
ROUND_FLOOR | 向负无穷方向舍入。如果为正数,舍入结果同ROUND_DOWN一致;如果为负数,舍入结果同ROUND_UP一致。注意:此模式不会增加数值大小。 |
ROUND_HALF_UP | 向“最接近”的数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。如果舍弃部分>= 0.5,则舍入行为与ROUND_UP相同;否则舍入行为与ROUND_DOWN相同。也即“四舍五入”。 |
ROUND_HALF_DOWN | 向“最接近”的数字舍入,如果与两个相邻数字的距离相等,则为向下舍入的舍入模式。如果舍弃部分> 0.5,则舍入行为与ROUND_UP相同;否则舍入行为与ROUND_DOWN相同。也即“五舍六入”。 |
ROUND_HALF_EVEN | 向“最接近”的数字舍入,如果与两个相邻数字的距离相等,则相邻的偶数舍入。如果舍弃部分左边的数字奇数,则舍入行为与 ROUND_HALF_UP 相同;如果为偶数,则舍入行为与 ROUND_HALF_DOWN 相同。也称为“银行家舍入法”。 |
ROUND_UNNECESSARY | 不需要舍入模式 |
BigDecimal 的比较
不要用equals方法来比较 BigDecimal 对象,因为它的 equals 方法会比较 scale,如果 scale 不一样,它会返回 false。
/*
b1大于b2返回1
b1等于b2返回0
b1小于b2返回-1
*/
int result = b1.compareTo(b2);
其他常用方法
判断正负
/*
b1为正数返回1
b1为零返回0
b1为数返回-1
*/
b1.signum();
对 BigDecimal 进行截断
//第一个参数是保留的小数点后位数,第二个参数为舍入模式
b1.setScale(3, RoundingMode.HALF_UP);