来自公众号:印记中文

本文由扇贝的前端工程师景国凯撰写,跟随作者一起了解浮点数的计算过程,掌握为何会出现精度丢失的根本原因。

之前简单介绍了二进制下整数的加减乘除基本运算,建议没看过的先去了解一下,这一节,一起探索一下浮点数的在二进制中是怎么进行基本运算的

打开chrome控制台,给一个特别简单的输入如下:

0.1+ 0.2// 0.30000000000000004

不知道你有没有吃惊,这么简单的一个计算,无论在js中还是在python中,都不是准确的0.3,这是为什么呢?

缘起

要了解这个问题,首先我们需要知道浮点数在计算机中到底是如何进行存储的?不知道你是怎么想的,总之我开始的第一反应就是假设是32位的存储空间,我可能会按照整数的存储方式去想象,比如1-24位是整数位,剩余的8位代表小数,这样可以吗?当然是可以的,但是先考虑下下面的这个问题:

想象这个白板是所能放置的数字的最大空间,现在有个问题,当我们想继续加0的时候,发现放不下了,因为空间是有限的,这个时候,我们会怎么办?

对,没错,科学计数法,就是我们在学习过程中,如果位数太多,我们一般都会用科学计数法来表示,这样的好处是,书写的位数小,表示的位数多,所以,回到计算机中,32位来表示实数的话,最多能表示多少位?2^32次方个,大约就是40亿,40亿数字很多吗?多,但是和无限多的实数集来比,沧海一粟,不够看的,所以计算机的设计者就要考虑这个问题了,如何让计算放下更多的数字?

真的有“定点数”

还记得上面说的,1-24表示整数位,剩余的表示小数位吗?这种存储方式就叫定点数,1-24位每4位表示一个0~9的数字的话,可以有6位表示整数部分,剩余2位表示小数部分,这样我们可以用32位表示从0到999999.99这样1亿个实数,这种用2进制来表示10进制的方式,叫做BCD编码(Binary-Coded Decimal)(https://zh.wikipedia.org/wiki/%E4%BA%8C%E9%80%B2%E7%A2%BC%E5%8D%81%E9%80%B2%E6%95%B8),比如说8421码,从左往右的权依次是8,4,2,1,等等,有兴趣的可以去了解一下。

“定点数”存在哪些问题

定点数有几个明显的缺点:

  • 占了很大的位数,但是能表示的数字范围却是有限的;

  • 无法同时表示很大的数字和很小的数字

其实究其根本原因,还是这种方式的“有限”限制了它,那么有没有一种方式,可以让32位所能表示的数字,更“无限”一点,更适合我们的诉求?

当然,设计计算机的前辈智慧是无限的~

浮点数是如何表示的

就像使用科学计数法一样,计算机前辈在浮点数的设计中也用了一样的思想,IEEE的标准定义了2个基本的浮点数格式,一个是32位的单精度浮点数,一个是64位的双精度浮点数,也就是float或float32和double或float64这两个数据格式,双精度和单精度的表示形式是差不多的,我们以单精度的作为了解和学习。

分为3部分:

  1. 第一部分是符号位,用s表示,代表正负,要记住的是在浮点数的范围内,所有数字都是有符号的;

  2. 第二部分是指数位,用e表示,代表指数,用8位bit表示的数字范围是0~255,为了同时表示大数和小数,我们把0~255去掉头尾(0,255后面会用到)的1~254去映射到-126~127,这样同时可以表示最大最小数字;

  3. 第三部分是有效数位,用f表示,代表的是有效的数位;

综合上述表示和科学计数法,我们的浮点数就可以表示为公式

(-1)^s * 1.f * 2^e

看完公式有没有发现问题?你会发现,我们这个公式无法表示0,的确,这是一个巧妙的设计,我们用0(8个bit都为0)和255(8个bit都为1)来表示一些特殊的数值,可以认为他们2个是特殊的flag位,比如当e和f都为0的时候,我们就认为这个浮点数是0,看下表:

以0.5为例,0.5的符号位s是0,f也是0,e是-1,这样(-1)^0 * 1.0 * 2 ^ -1 = 0.5

用32位bit表示就是

s e f
0 0111 1110 0000 ...0
1位 8位 23位 0.5

通过这样的表示方式,可以明显的发现32位所能表示的实数范围是很大的,又因为这种方式创建的实数中小数点的位置是可以”浮动“的,所以也被叫做浮点数,

到这里我们知道了浮点数是怎么存储的了,但是还没解决我们开始的问题,为何0.1+0.2!=0.3,首先我们要知道0.1是怎么存储的:

(-1)^s * 1.f * 2^e = 0.1

求解e

s=0 f=0 e=Math.log2(0.1) // -3.321928094887362

可以看出来这里0.1是算不出来一个准确数字的,从0.1到0.9只有0.5是可以求出一个准确的值的,剩下的都算不出来一个准确的值,这也就是为什么0.1+0.2会导致的精度问题,也就是说浮点数无论是表示还是计算其实都是近似计算,而近似计算就一定会导致一些问题,比如,你希望银行给你存钱以及算利息的时候用浮点数计算吗?当然不希望,否则你的钱算多了还好,算少了岂不是亏大了~

浮点数&二进制

把一个二进制表示的浮点数(0.1001),转为10进制表示,因为小数点后的每一位都表示的是2的-N次方,因此转为10进制就是:

(1 * 2 ^ -1) + (0 * 2 ^ -2) + (0 * 2 ^ -3) + (1 * 2 ^ -4) = 0.5625

可以理解为,对于二进制转十进制来说,从小数点开始,往左就是把2的指数从0开始过一位+1,包括0,往右就是从-1开始依次-1。

把一个10进制的浮点数,转为二进制的话,和整数的二进制表示采用“除以 2,然后看余数”的方式相比,小数部分转换是用一个相似的反方向操作,就是乘以2,然后看是否大于1,如果大于1就记下1并把结果减去1,一直重复操作。

比如,十进制的9.1,小数部分0.1转为2进制的过程为:

这是得到一个无限循环的部分”0011“,整数部分9转为二进制就是1001,因此结果就是1001.000110011...

把小数点做移3位,得到一个浮点数的结果是 1.001000110011... * 2 ^ 3

找到我们上面的公式 (-1)^s * 1.f * 2^e 套公式可得到:

s = 0 f = 00100011001100110011 001(到23位后自动舍弃,因为最长只能放23位有效数字)

指数位是3,我们e的范围是1-254 对半分正数和负数,所以127表示0,从127开始加3,得到结果是130,130转为二进制表示结果就是:1000 0010, 所以得到e=1000 0010, 结果如下:

所以最终的二进制表示结果是:0100 0001 0001 0001 1001 1001 1001 1001

如果我们再把这个浮点数表示换算成十进制, 实际得到的准确值是 9.09999942779541015625。相信你现在应该不会感觉奇怪了。

小心你的“存款”

首先,我们了解一下浮点数的加法计算过程是怎么样的,拿0.5 + 0.125来做计算,首先0.5套用公式计算结果是:

s = 0 有效位1.f = 1.0000... e = -1;

0.125 转换为:

s = 0 有效位1.f = 1.0000... e = -3;

然后,计算口诀是 指数位先对齐(小转大,这里要把e统一为-1), 然后按位相加符号位和有效位,e保持统一后的结果,因此:

符号位s 指数位e 有效位1.f
0.5 0 -1 1.0
0.125 0 -3 1.0
0.125对齐指数位 0 -1 0.01
0.5 + 0.125 0 -1 1.01

结果就是 (-1)^0 * 1.25 * 2^-1 = 0.625;

ps:为啥是1.25?虽然我们计算得出的是1.01 但是不要忘记计算是通过2进制算的,计算十进制的时候要转回来哦,所以0100000.... 后面都是0不用管,小数部分,从头开始乘以2的-N次别忘了,所以结果就是2^-2 = 0.25 加上整数位的1 就是1.25了~

可以发现,其实浮点数的计算过程,通过一个加法器也是可以实现的,电路成本同样不会很高,但是需要注意一些别的问题:

计算过程中,需要先对齐,但是有效数位的长度是23位,假如有一个很大的数字和一个很小的数字进行相加,然后对齐的过程中,小数被0部位过程中直接溢出了,23位不够用了,就会出现问题,补完后一些有效位被丢掉了,从而导致结果上的误差,两个数的指数位差超过23,比如到2^24位(差不多1600万倍),这2个数相加后,结果就直接是较大数,较小数完全被抛弃了。。。

有些同学会急急忙忙去chrome的控制输入下面的代码:

Math.pow(2, 24) + 0.1// 16777216.1

骗人,结果不是还有0.1吗,别急,小伙伴,js内置的Number是64位的,你可以试试

Math.pow(2, 50) + 0.1// 1125899906842624

是不是小数没了?【这种现象也叫大数吃小数】

所以如果银行采用IEEE-754 32位的浮点数计数方法来保管存款的话,假设你是一个大老板,你的账户中有2000万rmb,这个时候你的某一个员工给你打了1块钱,哈哈对不起,银行给算丢了,你的存款是不变的!所以,一般银行啊,电商一类的都会在涉及到钱的时候使用定点数或者整数来计算,避免出现精读丢失的问题,如果你去银行涉及数据库,一定要小心谨慎~

总结

这篇文章我们从浮点数的表示开始,到存储,到转换以及计算过程分析了真实的计算机世界中浮点数到底是怎么运行的,从中也了解了浮点数究竟为何会丢失精度:

  1. 浮点数在存储的时候可能出现不能准确转化为对应2进制的情况

  2. 在计算过程中,又存在大数吃小数的可能,也会导致数据不准确


●编号659,输入编号直达本文

●输入m获取文章目录

C语言与C++编程

分享C/C++技术文章

verilog 浮点转定点_浮点数0.1+0.2为何不等于0.3相关推荐

  1. verilog 浮点转定点_定点数和浮点数

    定点数 定点数是指,数字在小数点之后和之前具有固定的位数. 可以用Qm.n表示法进行表示. m位为整数部分 n位小数部分 有符号数的总位数N = m + n + 1 当n=0时,则定点数用来存储整数. ...

  2. verilog 浮点转定点_定点数优化:性能成倍提升

    定点数这玩意儿并不是什么新东西,早年 CPU 浮点性能不够,定点数技巧大量活跃于各类图形图像处理的热点路径中.今天 CPU 浮点上来了,但很多情况下整数仍然快于浮点,因此比如:libcario (gn ...

  3. input 0.1无法相加_你真的知道0.1+0.2为何不等于0.3吗?

    打开chrome控制台,给一个特别简单的输入如下: 0.1 + 0.2 // 0.30000000000000004 复制代码 不知道你有没有吃惊,这么简单的一个计算,无论在js中还是在python中 ...

  4. 判断一个doule等于0的正确方法

    doule进行数学运算时会出现精度问题,判断double是否等于0是不能用"d==0" 要用下面的方法: public static void main(String[] args ...

  5. 【问题思考总结】拉格朗日法的条件极值中的λ可以等于0吗(三种方法)

    问题 在做这道题的时候,我在对变量消元的时候,直接放弃了λ=0的情况,原因很简单,这个λ=0不是就是无条件极值了嘛,怎么可能呢?然而经过查阅资料和思考发现,并不是这样.于是在前人的基础上通过比较无条件 ...

  6. verilog中的定点数、浮点数、定点小数、定点整数的表示及运算

    1.定点数: 顾名思义定点数就是小数位固定不变的数叫做定点数,也就是小数点是定在某个位置不变的数. 2.定点数的分类: (1)定点整数:定点整数的小数点后面没有其他的数值,即小数点定在了数的最后面 定 ...

  7. 【定点和浮点】定点数与浮点数的解释

    目录 进制表示 实数表示 定点表示 浮点表示 进制表示 数字计算机使用二进制数字系统来表示计算机内部所有类型的信息.字母数字字符使用二进制位(即 0 和 1)表示.数字表示更易于设计,存储更容易,准确 ...

  8. 浮点与定点的二进制存储

    1.浮点数和定点数存储 https://blog.csdn.net/niaolianjiulin/article/details/82764511 2.浮点转定点 本篇主要介绍另外一种浮点转定点的方式 ...

  9. matlab实现浮点转定点,浮点转定点方法总结.doc

    浮点转定点方法总结 浮点转定点方法总结 -孔德琦 目录 定点运算方法3 1.1 数 的 定 标3 1.2c语言:从浮点到定点4 1.2.1 加法4 1.2.2乘法6 1.2.3除法7 1.2.4 三角 ...

最新文章

  1. MyBatis 源码分析 - SQL 的执行过程
  2. 研究人员的AI技术能够实时匹配活页乐谱与MIDI音频
  3. keras_14_初始化Initializers
  4. JAVA_if或者怎么用,Java If语句
  5. 8 线程安全且高效的单例模式
  6. hadoop 二次开发DatanodeWriteTimeout设置
  7. 半自动化运维之快速连接到指定环境(一)
  8. 人物和背景分离的快速方法
  9. python dlib opencv人脸识别准确度_基于dlib和opencv库的人脸识别
  10. 并发---ConcurrentHashMap
  11. [Python+Django]Web图书管理系统毕业设计之源码+论文篇
  12. Google Maps API(Flash 版)- Flash CS3 教程
  13. python时区转换_Python pytz时区转换
  14. acwing 1904 奶牛慢跑
  15. RecyclerView侧滑删除
  16. 谷歌浏览器调用打印机不预览
  17. Python 运维自动化之服务器信息采集
  18. BootStrap-Table分页参数传不到后台,后台接收分页参数为null
  19. Java基础高频面试题
  20. 一些Perl例程(全部手打并执行过)

热门文章

  1. 【JavaScript】 Webpack安装及文件打包
  2. C#验证子网掩码的正确性
  3. ubuntu 下安装nginx
  4. 红旗桌面版本最新运用体式款式和成就解答100例-8
  5. 一款轻量级的桌面WebServer通讯组件
  6. ASM - 条件判断
  7. SpringWeb 系列教程 RestTemplate 4xx/5xx 异常信息捕获
  8. 在Ubuntu17.04中遇到无法清空回收站解决方法
  9. VS2012 无法打开文件“kernel32.lib”问题的解决办法
  10. angular 前端路由不生效解决方案