前言
上篇已经讲了原码、反码和补码的出现解决了计算机对整数的存储和计算问题,而小数的存储和计算又是另外一套机制,对于人类而言,整数和小数的计算一样简单,然而对于计算机来说小数运算比整数运算要复杂的多。本文从浮点数原理出发,聊聊浮点数的精度问题,对网上的一些结论进行回答。
正文
在正式开讲之前,我们必须先同步几个概念:
移码
同原码、反码、补码一样,移码也是一种数字的编码方式。按照百度百科的定义,移码是符号位取反的补码,我认为这样不好理解,很容易给人造成误解认为移码必须在补码的基础上进行计算的,其实移码就是对负数加上一个常数 2^(n-1),把这个负数本身转换为一个正数,再以正数编码。
定点数
我们知道计算机只能记录0和1,是无法记录小数点的,那么在4位计算机中我们如何存储和计算二进制数1和0.1呢?为了解决小数的存储和计算问题,我们和计算机约定小数点在第2位和第3位之间,这样计算机就把1存为0100,0.1存为0010,1 0.1的加法就变成了0100 0010=0110,然后我们再按照约定加回小数点后变为01.10,即1.1。在上面过程我们看到小数点的位置是固定的,我们称之为定点数。定点数的缺点显而易见,表达形式过于僵硬,难以表达特别大的数或者特别小的数,于是浮点数出现了。
浮点数
浮点数标准也被称为IEEE二进制浮点数算术标准,浮点数由“符号”、“指数”和“尾数”3部分构成,其表达式如下: 数值 =(符号)尾数 ×底数^指数; 我们可以简单粗暴的理解浮点数与定点数相比,浮点数是指一个数的小数点的位置不是固定的而是可以浮动的,是利用科学计数法来表达的实数。 浮点数分为单精度和双精度,其存储结构如下图(平板手绘的草图,凑合看…..):
以32位单精度浮点数为例:
1:符号位:占1位,用0表示正数,1表示负数;
2:尾数位:占23位,根据浮点数标准,浮点数整数部分一定为1,因此可以省略不存,尾数部分存储二进制小数的小数部分,例如位数为0110实际上表示二进制小数1.0110;
3:指数位:即阶码,占8位,使用偏移量位127的移码表示(个人理解:根据移码定义偏移量应该为2^(n-1)=128,n=8;但是浮点数的尾数是规格化的,整数位总是1,也就是说浮点数的尾数已经右移一位了,因此这里减去1,偏移量设置为127)。IEEE标准通过指数将表示空间划分成了三大块:
1)最小值指数(所有位全置0)用于定义0和弱规范数(这里比较有意思,由于尾数有个隐藏的1,所以尾数无法表示0,只能用指数为0来特殊表示0);
2)最大指数(所有位全值1)用于定义±∞和NaN(Not a Number);
3)其他指数用于表示常规的数,也就是8个指数位实际上00000001(1)~11111110(254)用于表示常规整数,
假设一个32位单精度浮点数为:01000000 11000000 00000000 00000000;符号位是0,表示正数;指数位是10000001,减去127等于2;尾数位是10000000000000000000000,表示0.1,加上隐藏的整数部分1即为1.1;那么这个2进制浮点数位 1.1*2^2=110,转换为10进制即为5。
明白了以上基础知识,下来我们可以深入讨论几个问题了,相信在学习之初也和我有同样的疑问:1)指数是整数,为什么不同整形类型一样使用补码而要使用移码呢?2)为什么浮点数会丢失精度?3)为什么说浮点数的有效数字有6位?
指数为什么使用移码而不是补码
还记得我们学习科学记数法时,两个使用科学记数法表示的数字进行计算,第一步就是对阶,即比较两个数指数的大小,如果不相等则通过移动指数较小数字的小数点位置使两个数的指数相等,然后再对小数部分进行加减计算。使用移码是为了方便比较大小,如果使用补码则需要考虑符号位的影响,而使用移码只需要从左向右逐位比较即可,更为通俗的讲,移码是连续的,而补码不是连续的,移码更方便比较大小,如下图所示:
为什么浮点数会丢失精度
导致浮点数丢失精度的原因有很多,这里举两个例子: 1)10进制小数转二进制小数 我们知道10进制小数转二进制小数的方法是乘以2取整数,假设计算机可以存4位尾数。我们把0.4转换为2进制来看看: 0.42=0.8 取0 0.82=1.6 取1 0.62=1.2 取1 0.22=0.4 取0 0.42=0.8 取0 0.82=1.6 取1 …… 由于只能保存4位尾数,则规格化后表示为1.10012^(-2); 我们再把1.10012^(-2)转换为10进制小数: 02^(-1) 12^(-2) 12^(-3) 02^(-4) 02^(-5) 12^(-6)=0.390625; IEEE浮点数是不连续的离散值,受存储位数限制,浮点数并不能精确的表示所有的10进制小数,会丢失精度;
2)浮点数计算时为了对阶会对尾数右移,右移几位就会丢弃掉几位,这也是导致丢失精度的原因。
为什么说浮点数的精度是6位
这里这样说不精确,正确的说法是32位单精度浮点数有6位有效数字,百度会发现网上很多地方说为6位的原因是尾数占23位,2^23=8388608,可以完全覆盖6位数,这个理由是错误的。
前面我们已经说了32位浮点数的尾数有23位,但是还有1位隐藏的1,所以位数应该是24位,24位能够表示的最大数字为 2^24 -1=16777215,看起来所表示的范围能够完全覆盖7位数,考虑到最后一位可能是舍入的结果,其有效数字也就是6位了。
浮点数很复杂,以上仅仅是讨论了标准浮点数,还有非标准浮点数,这里就不展开讨论了(ps:明天又要上班了::>_<::)。