二进制浮点数的精度丢失问题
二进制浮点数的精度丢失问题
二进制浮点数介绍
二进制组成部分
二进制对应的:
名称 | 解释 |
---|---|
符号位 | 1位正数 0位负数 |
指数位 | 到0~255 一共8位 映射到 -126~127 0和255(有特殊用途 NAN和无穷大小的判断) |
有效位 | 一共23位 |
丢失精度的情况1
十进制数0.3+0.6,因为计算机都是先转换成二进制进行计算,所以我们先把0.3和0.6转换成二进制
这里我们直接用转换工具
0.3的二进制数:
乘二进位决定二进制为为 0或1,小数部分乘以2,大于1则二进制位为1,反之为0。
例:有效数字小数部分转化是0.3×2进制位为1,如果小于1,则二进制位为0,比如0.3×2等于0.6那么第一位就为0,0.6×2=1.2,那么第二位二进制位就为1,然后1.2的小数部分0.2×2又是0,所以第三位二进制位为0… 前三位010
和上面的0.010匹配,有兴趣可以自行计算下去,当然这个计算不可能算完的,因为只要小数部分为0.6,又回到起点,所以这是个无线循环乘法,所以丢失精度是自然的。
因为浮点数的有效长度是23位,所以有效长度为
0.01001100110011001100110
其中0.我们可以忽略掉
下面我们来看0.6的二进制表示
名称 | 解释 |
---|---|
0.6*2=1.2 | 1 |
0.2*2=0.4 | 0 |
0.4*2=0.8 | 0 |
0.8*2=1.6 | 1 |
0.6*2=1.2 | 1 |
对应了上面的0.10011 ~10011 其中前头的0.我们可以忽略掉 ,只是表示这个是小数
有效位是23位
0.10011001100110011001100
0.3+0.6结果:
二进制形式:
十进制形式:
我们可以看出0.3+0.6等于=0.899999999…
我们再来看一组数据0.3+0.4
0.3我们就不进行二进制分析了。和上面一样
我们来分析0.4转二进制
名称 | 解释 |
---|---|
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.01100 ~01100 其中前头的0.我们可以忽略掉 ,只是表示这个是小数,后面的数值可以自行计算
0.4的23位有效二进制如下图
0.01100110011001100110011
此时我们把0.3和0.4 二进制进行相加
我们将0.3与0.4进行 的二进制进行相加
为什么一个是0.7000一个是6.9999 开头的
很明显有问题,经过测试发现是0.3的二进制没有保留位数(二进制保留)即将第二十四位如果为1则直接向前进1(大部分情况是这样),因为0.3的第24位是1,所以0.3转二进制实际为
0.01001100110011001100111
再和0.4相加的结果
和上面的0.7000000(0.7000000476837158)四舍五入得到。
还有一种丢失精度的情况
我们知道浮点数有效位数为23位,如果两个数相加,或者一个数的有效位超过了23位,那么超过23位的精度将全部丢失
20000000转2进制,如图 位数一共有25位,最后两位作为科学计数法被抹去了,如果我们此时+1,是不是在
1001100010010110100000000 +1呢?
答案很明确。但是+1没效果,因为一共25位,只会保留23位,但是如果我们+2或者3就有效果了
加2和3有效果:
效果不如意,因为+2和3会发生进位,第24位为1会进位。有效果是有效果,但是数据可能偏差大
下面这个算法能解决精度丢失的问题
Kahan Summation算法
public class KahanSummation {
public static void main(String[] args) {
float sum = 0.0f;
float c = 0.0f;
for (int i = 0; i < 20000000; i++) {
float x = 1.0f;
float y = x - c;
float t = sum + y;
c = (t-sum)-y;
sum = t;
}
System.out.println("sum is " + sum);
}
}
综上所述:浮点数用于一些不需要很准确的计算。如果涉及到钱等一些数据可以用bigdecimal,数据库用decimal(12,2)函数。
上一篇: Linux内存地址的分段、分页机制(上)
下一篇: github实用的搜索小技巧