组成原理|为什么计算机中0.3 + 0.6 等于 0.899999999...?

浮点数的不精确性

打开浏览器控制台 输入 0.3 + 0.6 ,结果输出了0.8999999,不相信你可以按F12打开控制台试一试O(∩_∩)O

这是为什么?
我们先看下面的介绍,最后文末会给出答案。

定点数的表示

使用BCD编码:用二进制来表示十进制的编码方式

编码过程:我们用 4 个比特来表示 0~9 的整数,那么 32 个比特就可以表示8个这样的数
然后我们把最右边的 2 个 0~9 的整数,当成小数部分;
把左边 6 个 0~9 的整数,当成整数部分。
这样,我们就可以用 32 个比特,来表示从 0 到 999999.99 这样 1 亿个实数了。

适用途径:银行、商家精确到“分0.01 ”的交易。
缺点:浪费比特啊,而且这样的表示方式没办法同时表示很大的数字和很小的数字。

浮点数的表示

IEEE的标准,它定义了两个基本的格式:float单精度 和 double双精度

下面是单精度的表示方法:

1.符号为表示正负0和1。
2.指数位,因为我们也需要表示很小的数,指数位置需要负数,所以我们在这里用 1~254 映射到 -126~127 这 254个有正有负的数
3.有效数位,是一个 23 个比特组成的有效数位。我们用f来表示。

举了栗子:

浮点数相加的精度误差

栗子1:让一个值为 2000 万的 32 位浮点数和 1 相加,你会发现,+1 这个过程因为精度损失,被“完全抛弃”了。

float a = 20000000.0f;
float b = 1.0f;
float sum = a + b;
print sum //发现sum的值还是2000万,而不是 200000001

为什么出现上述情况,因为两个浮点数相加的运算是需要移位的,
过大或者过小的数移位之后可能会出现有效位数消失的情况,此部分内容可以搜寻相关资料:浮点数相加运算过程、浮点数的二进制表示

栗子2:将一个等于1.0的浮点数累加循环加2000万次,发现结果是1600万左右,你可以用java实现下面代码,看看结果~~

为什么?也是浮点数加法产生的精度误差


    float sum = 0.0f;
    for (int i = 0; i < 20000000; i++) {
    	float x = 1.0f;
    	sum += x;    	
    }
    print sum // sum 约等于1.6777216E7

解决方案:Kahan Summation 算法


    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;    	
    }
   print sum;//2000万

在每次的计算过程中,都用一次减法,把当前加法计算中损失的精度记录下来然后在后面的循环中,把这个精度损失放在要加的小数上,再做一次运算。
该算法的数学证明参见:Wikipedia 链接

所以回到文章开头的问题,0.3 + 0.6 为什么不等于0.9,
就是因为计算机用浮点数表示法来表示浮点数,只能精确表示 2^x (2的x次方)这种数0.3、0.6不能精确表示出来,例如0.5是2^(-1)是可以被精确表示的。

posted @ 2019-06-19 09:31  fishers  阅读(3262)  评论(4编辑  收藏  举报