在Java中使用浮点数进行运算时会发生精度丢失的问题。先看实例:
我们想要的结果是:
v的值为6.2; (d3-d2)==(d2-d1) 为true;
为什么Java中小数精度会丢失?
大家都应该都知道电脑的底层就是二进制吧,所有的操作最终都是在二进制中进行的,内存中只有0和1;
不知道的见 我的上一篇 彻底搞懂二进制的文章!
知道怎么转化和计算的,记住一个核心:二进制表示的小数是精确的而不是确切的
1、小数的二进制转化:
例:0.7的二进制为:
0.7 * 2 = 1.4 取整数部分1 0.4 * 2 = 0.8 取整数部分0 0.8 * 2 = 1.6 取整数部分1
0.6 * 2 = 1.2 取整数部分1 0.2 * 2 = 0.4 取整数部分0 0.4 * 2 = 0.8 取整数部分0
……等
0.7的二进制为(正向取整)101100…
上面的进制转化计算出现了循环也就是说乘2永远无法等于整数而不是小数。
所以二进制表示的小数是精确的而不是确切的。
小数无法确切的储存在二进制中,而小数计算是通过二进制完成的,所以出现了精度丢失。
2、Java浮点计算精度丢失解决方案
我们如何解决这个问题呢?一种很常用的方法是:使用使用 BigDecimal 来定义浮点数的值,再进行浮点数的运算操作。
1、运算
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); /* 0.1 */
System.out.println(y); /* 0.1 */
System.out.println(Objects.equals(x, y)); /* true */
2、数值比较
a.compareTo(b)
: 返回 -1 表示 a
小于 b
,0 表示 a
等于 b
, 1表示 a
大于 b
。
BigDecimal a = new BigDecimal("2.0");
BigDecimal b = new BigDecimal("0.3");
System.out.println(a.compareTo(b));// 1
3、保留几位小数
通过 setScale
方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA会提示。
BigDecimal m = new BigDecimal("2.125433");
BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN);
System.out.println(n);// 2.125
4、注意事项:
注意:我们在使用BigDecimal时,为了防止精度丢失,推荐使用它的 BigDecimal(String) 构造方法来创建对象。《阿里巴巴Java开发手册》对这部分内容也有提到如下图所示。
5、扩展:
BigDecimal 主要用来操作(大)浮点数,BigInteger 主要用来操作大整数(超过 long 类型)。
BigDecimal 的实现利用到了 BigInteger, 所不同的是 BigDecimal 加入了小数位的概念
6、自定义运算工具类
对BigDecimal包装后的基本运算,具体使用方法如下:
/**
* 加法
* @param m1
* @param m2
* @return
*/
public static double add(double m1, double m2) {
BigDecimal p1 = new BigDecimal(Double.toString(m1));
BigDecimal p2 = new BigDecimal(Double.toString(m2));
return p1.add(p2).doubleValue();
}
/**
* 减法
* @param m1
* @param m2
* @return
*/
public static double sub(double m1, double m2) {
BigDecimal p1 = new BigDecimal(Double.toString(m1));
BigDecimal p2 = new BigDecimal(Double.toString(m2));
return p1.subtract(p2).doubleValue();
}
/**
* 乘法
* @param m1
* @param m2
* @return
*/
public static double mul(double m1, double m2) {
BigDecimal p1 = new BigDecimal(Double.toString(m1));
BigDecimal p2 = new BigDecimal(Double.toString(m2));
return p1.multiply(p2).doubleValue();
}
/**
* 除法
* @param m1
* @param m2
* @param scale
* @return
*/
public static double div(double m1, double m2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("Parameter error");
}
BigDecimal p1 = new BigDecimal(Double.toString(m1));
BigDecimal p2 = new BigDecimal(Double.toString(m2));
return p1.divide(p2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}