1.IEEE单精度浮点数
1.1 表示方式
作为一个“正经”的CPU,运算使用的单精度浮点数也就是C语言中的float类型数据,在存储器、寄存器等位置存储形式都是这样的:
图 IEEE 单精度浮点表示方式
1 bit 符号位:很明显,表示正负的
8 bit 指数位: 以2为底的指数
23 位尾数位:理解成有效数字位(2进制)
1.2 手算浮点数
代表的浮点值的计算方式如下表:
图 单精度浮点数值计算
如果觉得看的费劲儿,举个例子:
例子1:寄存器\内存里的32位内容是 0b1_10000000_10000000000000000000000,这代表的浮点数是多少?
答:8位指数位,不全为0,也不全为1,所以这是规范化数,符号位为1,则判断是规范化负数,则计算公式为 ,计算结果为-3。
例子2:寄存器\内存里的32位内容是 0b1_11111111_10000000000000000000000,这代表的浮点数是多少?
答:8位指数位全为1,则浮点数为正负无穷大、NaN(Not a Number)中之一,因为尾数位不等于0,所以这代表的浮点数为NaN。
例子3:寄存器\内存里的32位内容是 0b1_11111111_00000000000000000000000,这代表的浮点数是多少?
答:8位指数位全为1,则浮点数为正负无穷大、NaN(Not a Number)中之一,因为尾数位等于0,所以这代表的浮点数为无穷大,因为符号位为1,所以表示的是负无穷大。
例子4:寄存器\内存里的32位内容是 0b0_00000000_10000000000000000000000,这代表的浮点数是多少?
答:8位指数位全为0,且尾数不为0,所以这是一个非规格化数,其值为,可以注意到,非规格化浮点数都是绝对值很小的数。
1.3 单精度浮点数的有效位数
教科书上说,单精度浮点数具有6~7位有效位数(十进制),这是怎么来的呢?
下图为编写这篇博客时百度百科对此的解释:
图 百度百科对单精度浮点数解释
这个解释明显是错误的,例如定义浮点数134,217,728,其有效位数为9位,那么计算机会将其四舍五入嘛?
实际上并不会,仍然保持9位有效数字。那么6到7位的有效数字是怎么来的呢?
其实上面的数字134,217,728是精心设计过的,实际上134,217,728到134,217,744(左闭右开区间)的范围的实数用单精度浮点数表示都是134,217,728(0x4D 00 00 00)
原因如下:
根据1.2节的 图 单精度浮点数值计算,可以知晓,尾数最多的数字应为规格化数,其二进制有效位数最大为 (1 + 23)位,对应十进制数 ,介于
与
之间,也就是教科书上说的6~7位有效数字。
实际上,应该理解为单精度浮点数具有6~7位的可信位数,也就是说这个数字从左侧第一个不为0的位开始,至少可以保证向右6~7位是正确可以信赖的,再往后的位数就不能保证是对的了。
例如,之前提到的将134,217,743赋值给单精度浮点数变量,变量值会变为134,217,728,也就是说前7位'134,217,7XX'(不包括XX)是正确可信的,后两位'28'是错误的,不可信的。
大数吃小数
大数吃小数是指一个很大的数,加减一个很小的数,其数值不变。例如134217728 + 1 = 134217728 ;现在我们知道这个原理就是单精度浮点数有效(可信)位数只有6~7位,同样的双精度或其他精度的浮点数有他们各自的有效位数。只要相加减的两个单精度数的数量级差距超过了,就会出现大数吃小数的现象(浮点数加减运算时,较小的数要向较大的数的阶码对齐,对应较小数的尾数就要在寄存器中右移,当右移溢出位为1时,导致损失精度,也即有效位数损失了,也即可信位数损失了)。
从事算法相关领域落地实现时,忽略了这一点,会造成严重的错误(134217728 + 1 - 134217728 = 0,表现为错误),而不是误差(134217728 + 1 = 134217728 ,表现为精度不够)