参考: IEEE754标准和浮点数学习
参考: 深入理解浮点数有效位, 浮点数分布
参考: 知乎
IEEE754标准-规约形式的浮点数
由于浮点数分为规约形式的浮点数、非规约形式的浮点数、还有特殊值。 我们先不考虑非规约形式的浮点数和特殊值, 只考虑规约信息的浮点数, 下面将的是规约形式的单精度浮点数,最后讲另外两种浮点数
在维基百科中, 对浮点数的表示方式为:
1 | Value(值) = sign(符号位) x exponent(指数) x fraction(小数/分数值) |
对应下面这张图片
上面的图是表示的0.15625(十进制)这个数, 在计算机中存储的样子.
然后我们通过解释这张图来学习IEEE754标准
因为sign的0表示正, 因此它可能是这个样子的
1 | 2^n x 1.001011 |
我们知道, 如果n=10, 那么小数点向右移动10位, 如果n=-10, 那么小数点向左移动10位.
但是如果要根据上面的公式计算出图片中的这个值, 需要按照一定规则转换
sign这个就是符号位, 0代表正, 1代表负
exponent = 01111100 = 124(十进制). 但是这个值不能直接带入到2的n次方中使用, 因为指数这个值实际上等于指数真正的值加上一个偏移量后, 得到的偏移量, 来存储的(为什么要加偏移量, 后面会解释). 比如说, 实际指数是17(10进制), 那么存入到磁盘中的值, 需要加上偏移量127(10进制), 即144(10进制),把这个值转成二进制存入到上面图片中的exponent部分, 那么根据这个规则我们可以算出实际的指数是124-127= -3(十进制)
fraction要格外注意,图中01000000000000000000000直接转换为10进制为2097152,这是不对的(而且不能现在转10进制),因为在IEEE754标准中,01000000000000000000000代表的是小数部分:因为所有的数,在使用科学计数法表示的时候,都可以表示为http://1.xxx × 10的x方,所以上面图片的fraction部分是省略了1的,我们整合起来就是:1.01000000000000000000000。
综合上面的三个部分后, 我们知道这个算式应该是:
1 | x^{-1} x 1.01000000000000000000000 |
上面我们提到过, 根据指数n来移动小数点, 因此上面算式的结果等价于
0.00101000000000000000000000 = 0.00101
注意!这是二进制,我们把0.00101转成10进制为:0.15625。这就是开头我们说的结果啦。
知识点
- exponent称为”指数的偏移值”, 在中文中,常常称为阶码
- 阶码的取值范围? 由于阶码由8比特组成, 因此理论上可以表示的数值范围最大是2^8 = 256个数字. 但是实际上第一个最高位是代表符号(正负的), 因此去掉后的范围是2^7=-127到128之间, 但这个不是阶码的范围, 阶码是”指数的实际值”+”固定的偏移值”, 因此阶码的范围是1~254, 是的, 阶码肯定是大于0的
- 为什么要由”固定的偏移值”, 偏移值的大小是怎么来的? 很多地方的解释比较难以理解, 实际上它的好处就是上一条的结论, 就是偏移量的存在导致阶码肯定是大于0 的, 换句话讲就是我们可以用正数来表示负的指数. 这个带来的好处就是计算机在处理减法运算时由局限性, 采用移码的方式, 可以实现计算机最舒服的数据格式, 即a-b = a + (-b), 也就是计算机只会作加法.
- 这个”固定的偏移值”是怎么算出来的? IEEE754规定这个值为:
1 | 2^{e-1}-1 |
现在来定义一下规约的浮点数.
如果浮点数中的阶码范围在0< exponent < 2^e-2之间, 即上面说的[1, 254]这之间(指的是单精度浮点数), 并且在科学计数法下, 小数部分是以1开头的, 即1.xxxx, 那么这个浮点数被称为规约形式的浮点数.
IEEE754标准-非规约形式的浮点数
浮点数的范围是[1, 254]之间, 而非规约形式的浮点数, 就是阶码为0时.
上文提到的-127用作特殊处理了, 实际上用在这里了, 我们想: -127+固定偏移量127=0. 反过来讲, 因为非规约浮点数的阶码肯定为0, 那么0-127= -127, 也就是非规约形式的浮点数的指数永远是-127, 这实际是错误的, 因为IEEE754规定, 非规约形式的浮点数, 它的”固定偏移值”是126, 因此非规约形式的浮点数的指数的实际值 = -126和规约形式的浮点数的指数的实际值1一样, 都是-126
我们先说结论, 非规约形式的浮点数为: 阶码为0, 小数部分(fraction)不为0, 称为非规约形式的浮点数.
知识点
- 非规约形式的浮点数, 它的”固定值”不是127而是126
- 非规约形式的浮点数, 一般用于小数是0.xxxxx这样情况的小数部分(当然, 1.xxxx也是可以正常使用的, 我们指数规定接近于0才用非规约, 所以是0.xxxx)
特殊值
- 如果指数是0而且尾数的小数部分是0, 这个数±0(和符号位相关)
- 如果这个指数255, 并且小数部分是0, 这个数是正负无穷(和符号位相关)
- 如果指数=255, 并且小数部分为非0, 这个数就是非数(NAN)
浮点数的分布理解
理解这个的关键就是, 理解浮点数的小数点是浮动的.
单精度浮点数的范围就是[1, 254], 那么当这个数字越小时, 它可以移动的小数点产生的数字就越多, 因此越靠近中间能表示的数字就越多.
浮点数分布
尽管浮点数表示的范围很广, 但由于精度损失的存在, 加上幂次的放大作用, 一个浮点数实际上是表示了周围的一个有理数区间. 如果讲浮点数绘制到一个数轴上, 直观上看, 靠近0的部分, 浮点数出现比较密集. 越靠近无穷大, 浮点数分布越稀疏, 一个浮点值代表了周围一篇数据. 如下图所示, 从这个意义上来说, 浮点数不适宜直接比较相等, 它们是代表了一个数据范围. 实际应用中, 如果要使用浮点数计算, 一定要考虑精度问题. 在满足精度要求的前提下, 计算结果才是有效的.
舍入
IEEE浮点格式定义了四种不同的舍入方式, 下面以十进制的小数舍入只保留小数点后0位为例
| 方式 | 1.40 | 1.60 | 1.50 | 2.50 | -1.50 |
|---|---|---|---|---|---|
| 向偶数舍入 | 1 | 2 | 2 | 2 | -2 |
| 向零舍入 | 1 | 1 | 1 | 2 | -1 |
| 向下舍入 | 1 | 1 | 1 | 2 | -2 |
| 向上舍入 | 2 | 2 | 2 | 2 | -1 |
向偶数舍入这个方式咋看可能没看懂, 它其实是使舍入够的值的最低有效数字是偶数. 1.5舍入有两个选择: 1和2, 但由于2是偶数所以就舍入到2, 同样2.5舍入有两个选择: 2和3, 但由于3是奇数, 所以还是舍入到2.
向偶数舍入的方式使得在大多数情况下, 5舍去还是进位的概率是差不多的, 在进行一些大量数据的统计时产生的偏差相较其他方式小一些.
二进制舍入的例子与规则总结
假设我们要求值保留小数点后三位, 有以下例子:
- 1.0011舍入后:1.001, 原因: 1.001舍入后两个选择: 1.001和1.010, |1.001011-1.001| = 0.00011, |1.001-1.010| = 0.000101, 显然0.000 011<0.000101, 所以1.001比1.010更接近原值1.001011, 所以舍入到了1.001
- 1.001 101 舍入后: 1.010 原因: 1.001 101舍入有两个选择:1.001和1.010,|1.001 101 - 1.001| = 0.000 101,|1.001 101 - 1.010| = 0.000 011,显然0.000 101 > 0.000 011所以舍入到后者。
- 1.001 100 舍入后: 1.010 原因: 1.001 100舍入有两个选择:1.001和1.010,|1.001 100 - 1.001| = 0.000 100,|1.001 100 - 1.010| = 0.000 100,两种选择的差值是相同的,这时使用向偶数舍入的方式,1.010是偶数(0偶1奇),所以舍入到1.010
根据上面的例子我们总结出以下规律:
我们用RR…RDD…D来表示一个二进制小数, R表示保留位, D表示舍去位 那么有以下规则:
- DD…D < 10…0 直接舍去
- DD…D > 10…0 向上舍入
- DD…D = 10…0 向偶数舍入, 细则:
- RR…R = XX…0, 直接舍去
- RR…R = XX…1, 向上舍入
“最近原则”其实是”损失的精度最小”原则.
“偶数”原则其实是”舍入后保留的最低有效位是偶数(二进制表示则是0)”原则.