封面图
大家好,我是阿壮,一个在互联网苟且偷生的程序员,今天和大家分享一下开发中遇到的浮点数计算该怎么办?
为什么 0.1+0.2 != 0.3?
先看一个诡异的代码
浮点数的编码方式
首先我们需要知道在计算机的世界里,0.1+0.2 为什么不等于 0.3 的,大家可以自己尝试一下,这里就牵扯到了浮点数的编码方式,浮点数在计算机中的存储方式遵循 IEEE 754 浮点数计数标准,可以表示为
采用尾数 + 阶码的编码方式,符号(S)、阶码部分(E)、尾数部分(M)三个确定下来,就可以确定一个浮点数。
- 符号部分(S)
0-正 1-负
- 阶码部分(E)(指数部分):
对于 float 型浮点数,指数部分 8 位,考虑可正可负,因此可以表示的指数范围为-127 ~ 128
对于 double 型浮点数,指数部分 11 位,考虑可正可负,因此可以表示的指数范围为-1023 ~ 1024
- 尾数部分(M):
浮点数的精度是由尾数的位数来决定的:
对于 float 型浮点数,尾数部分 23 位,换算成十进制就是 2^23=8388608,所以十进制精度只有 6 ~ 7 位;
对于 double 型浮点数,尾数部分 52 位,换算成十进制就是 2^52 = 4503599627370496,所以十进制精度只有 15 ~ 16 位
所以浮点数交给计算机存储时可能会出现精度丢失的情况。记住 float 的精度上限是 6~7 位,double 的精度上限是 15~16 位。
浮点数十进制转二进制
举个例子 0.875 转换为二进制该怎么做?
- 以小数点将浮点数拆分为整数部分和小数部分
- 整数部分使用:除 2 取余法 即可,如果为 0,则无需操作
- 小数部分使用:乘 2 取整法 即可,计算过程如下
- 合并结果:整数部分+小数部分
最终得到二进制结果为 0.111
以下是 0.1 和 0.2 的二进制表示
0.1 的二进制 0.0001100110011001...(无限循环)
0.2 的二进制 0.0011001100110011...(无限循环)
此时我们知道我们看到的 0.1 并不是真正的 0.1,与此同时计算得到的结果也是无限不循环的。原来是因为计算机不能精确表示 0.1,0.2 这样的浮点数,计算时使用的是带有舍入误差的数,也就造成浮点数在进制转换中造成了精度丢失。
推荐使用 BigDecimal
既然浮点数精度会丢失,在实际业务中又经常需要精确到小数点后几位去计算,特别是还支付类的产品中通常都要精确到分,这时候我们就可以使用 Java 自带的 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);
// 比较大小
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.compareTo(b));
// 保留几位小数
BigDecimal m = new BigDecimal("1.255433");
BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN);
System.out.println(n);