BigDecimal详解

154 阅读2分钟

浮点数精度丢失

浮点数的运算常常会伴有精度丢失的风险,那么应该如何解决这一问题呢?

《阿里巴巴Java开发手册》中提出的解决办法:”为了避免精度丢失,可以使用BigDecimal来进行浮点数的运算“。

问题演示:

float x = 3.0f - 1.9f;
float y = 4.0f - 2.9f;
System.out.println("x = " + x);//x = 1.1
System.out.println("y = " + y);//y = 1.0999999
System.out.println(x == y);//false

double x = 3.0f - 1.9f;
double y = 4.0f - 2.9f;
System.out.println("x = " + x);//x = 1.100000023841858
System.out.println("y = " + y);//y = y = 1.0999999046325684
System.out.println(x == y);//false

为什么浮点数float或者double运算的时候会出现精度丢失的问题呢?

这和计算机保存浮点数的机制有关系,由于计算机在保存数据时是二进制的,宽度有限,当表示一个无限循环小数时,只能将数据截断,这样就造成了精度丢失的情况。

在《阿里巴巴Java开发手册》就已经提到: 浮点数之间的等值判断,基本数据类型不能使用 == 进行比较,包装数据类型不能使用 equals 进行判断。

BigDecimal 介绍

通常情况下,当业务设计到金钱,或者对数据精度有需求的业务时,就会通过BigDecimal 来计算。

想要解决浮点数精度丢失的问题,就需要用BigDecimal来定义所需要的浮点数,然后在根据需求进行对应的运算, 使用 BigDecimal 来定义值,再进行浮点数的运算操作 。

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
System.out.println("x = " + x);//x = 0.1
System.out.println("y = " + y);//y = 0.1
System.out.println(x.compareTo(y));//0

BigDecimal 常见的方法

创建

在使用BigDecimal 时,为了防止精度丢失,一般使用BigDecima(String val)构造方法或者BigDecimal.valueOf(double val)静态方法来创建对象。

一般 禁止使用构造方法 BigDecimal(double) 的方式把 double 值转化为 BigDecimal 对象

加减乘除

BigDecimal a = new BigDecimal("2.1");
BigDecimal b = new BigDecimal("0.7");
System.out.println("减法 " + a.subtract(b));//减法 1.4
System.out.println("加法 " + a.add(b));//加法 2.8
System.out.println("乘法 " + a.multiply(b));//乘法 1.47
System.out.println("除法 " + a.divide(b));//除法 3
System.out.println("除法 " + a.divide(b,2,BigDecimal.ROUND_HALF_UP));//除法 2.33  b=0.9

注意:当a/b除不尽时,就会抛出java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.的异常

当所需要的数据除不尽时 可以在divide(数据,保留小数位,保留规则)中添加所需要的信息

保留规则

public enum RoundingMode {
   // 2.5 -> 3 , 1.6 -> 2
   // -1.6 -> -2 , -2.5 -> -3
			 UP(BigDecimal.ROUND_UP),
   // 2.5 -> 2 , 1.6 -> 1
   // -1.6 -> -1 , -2.5 -> -2
			 DOWN(BigDecimal.ROUND_DOWN),
			 // 2.5 -> 3 , 1.6 -> 2
   // -1.6 -> -1 , -2.5 -> -2
			 CEILING(BigDecimal.ROUND_CEILING),
			 // 2.5 -> 2 , 1.6 -> 1
   // -1.6 -> -2 , -2.5 -> -3
			 FLOOR(BigDecimal.ROUND_FLOOR),
   	// 2.5 -> 3 , 1.6 -> 2
   // -1.6 -> -2 , -2.5 -> -3
			 HALF_UP(BigDecimal.ROUND_HALF_UP),
   //......
}

大小比较

a.compareTo(b) : 返回 -1 表示 a 小于 b,0 表示 a 等于 b , 1 表示 a 大于 b。

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.compareTo(b));// 1

保留几位小数

通过 setScale方法设置保留几位小数以及保留规则。

BigDecimal m = new BigDecimal("1.255433");
BigDecimal n = m.setScale(3,RoundingMode.HALF_DOWN);
System.out.println(n);// 1.255

BigDecimal 等值比较问题

在开发手册中也给出了详细的介绍,equals()比较不会忽略精度,而compareTo()则会忽略

总结

由于二进制不能完全的表示浮点数,所以引入了BigDecimal来解决需要对精度有要求的业务逻辑