【Java】BigDecimal类的使用

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 类的部分源码。

【Java】BigDecimal类的使用

  • intVal 字段是存放整数非标度值(放大了的整数)。当该 BigDecimal 的有效数的绝对值小于或等于Long.MAX_VALUE ,此字段为空。
  • scale 字段是标度(值的小数点后的位数)
  • precision 字段是此 BigDecimal 中的十进制位数(即整数非标度值的数字位数),如果位数未知(后备信息),则为 0。 如果非零,则保证该值正确。
  • intCompact 字段也是用来存整数的非标度值。如果该 BigDecimal 的有效数的绝对值小于或等于Long.MAX_VALUE ,则该值可以紧凑地存储在该字段中并用于计算。

【Java】BigDecimal类的使用

把 2.9 的放大成 29 作为非标度值,小数点后只有一位,故1 作为标度,这就是 BigDecimal。

BigDecimal 的构造

常用的两个构造器是

  • BigDecimal(String)
  • BigDecimal(double)

例如

BigDecimal b1 = new BigDecimal("2.9");
BigDecimal b2 = new BigDecimal(2.9);

更推荐使用通过字符串去构造 BigDecimal 。看以下运行结果

【Java】BigDecimal类的使用

参数类型为 double 的构造方法的结果有一定的不可预知性。利用 double 作为参数的构造函数,无法精确构造一个 BigDecimal 对象 ,而利用 String 对象作为参数传入的构造函数能精确的构造出一个 BigDecimal 对象。

也可以通过以下方法构建

BigDeciamal b3 = BigDecimal.valueOf(2.9);

【Java】BigDecimal类的使用

从其源码我们可以看出它本质是用参数类型为 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);
上一篇:Matlab中属性 get 方法的使用


下一篇:L1-027 出租 (20 分)桶排序和数组下标的完美配合