浮点误差 0.1+0.2≠0.3?

136 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第12天,点击查看活动详情

问题描述

你在计算机中输入了 0.1 + 0.2,但是计算机返回的并不是0.3,而是0.30000000000000004这个数字,你可能下意识以为这是个bug,然而在不同的语言之中得到的答案还是相同的,其实最终原因是在IEEE-754。

1.jpg

2.jpg

6.jpg

IEEE-754的小数表示法

如果是一个32位的存储单元,小数表示的过程是这样的: 先把二进制的小数转化为科学计数法表示(如1.XXXX * 2^n),把前面的1舍去,用存储单元的后面23位表示这个科学计数法的小数部分,第一位和整数表示是一样的表示符号位1为负0为正,第一位后面的八位数用来表示指数n,由于8位二进制数最多表示0~256,我们把127(01111111)表示指数0,则以他往上加就是指数0++,往下就是负数的指数。然后这就是IEEE的具体表示方法了,关于十进制小数如何转二进制,这个很简单,不断乘以二就可以更新二进制小数位了。

简单来说就是,因为二进制中只有0和1,那么转换成2进制科学计数法后第一个非0位一定是1,那么这个1不存在电脑里也是谁都知道的,于是科学计数法的1.×2ⁿ的1就不需要占空间存了,也就是说需要存的只有正负号,小数部分和指数部分n。

浮点存储格式,第一位是正负号1代表正数0代表负数,后面一串代表指数n,这个n也是二进制的方式存储,再后面是小数部分 ,同样2进制。 然后就是这两个一长串二进制格子是怎么把小数和指数存进去的。科学计数法的原理是,用指数部分表示对前面小数的小数点的左移右移几位(或乘几个10,几个1/10,比如123是1.23小数点右移三位,即乘2个10,于是写成1.23×10²。)同理二进制科学计数法就是乘几个2或几个1/2。比如3写成2进制是11,这个11的科学计数法就是1.1×2¹,表示11是由1.1小数点右移1位,即1.1乘了1个2。 (这里可能会因为不习惯2进制计算规则很难理解,可以举个例子,二进制11是十进制3二进制110是十进制6,3乘1个2得6所以二进制11×2得110;二进制1100是十进制12,3乘2个2得12所以二进制11×2²就是1100,每乘1个2小数点右移一位每乘1个1/2小数点左移一位)(此处为减小理解难度指数部分一律为十进制数) 然后就是小数部分是怎么表示的。小数的理解与整数相当割裂,原理上是一样的但表示出来很奇怪(原因是我们在用十进制数理解二进制)。

十进制数的二进制表示法

首先需要知道的是0.1,0.01等会表示进制分之1进制二次方分之1等以此类推,于是我们可以发现二进制0.1表示的是十进制0.5,二进制0.01表示十进制0.25,于是加一下十进制0.75是二进制0.11,那么我们可以怎么计算十进制小数转化为二进制小数呢?

4.jpg

5.jpg

0.5乘2得1,即0.5是1乘了1个1/2因此用二进制表示就是1×2⁻¹,即1的小数点左移1位得0.1,0.25乘2个2得1即0.25是1乘了2个1/2因此用二进制表示就是1×2⁻²,即1的小数点左移了2位得0.01,于是我们得到结论乘几个2得1,二进制就是1的小数点左移几次得到0.几个01,那这是刚好乘若干个2能得1的那不能的怎么办呢?比如0.75乘2个2得3啊,3是11啊,那0.75就是11小数点左移两位那就是0.11好嘛完全正确,看来这就是方法了。

3.jpg