浮点数精度丢失
浮点数的运算常常会伴有精度丢失的风险,那么应该如何解决这一问题呢?
《阿里巴巴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来解决需要对精度有要求的业务逻辑