IEEE754
一、科学计数法
光速 = 2.99792458 × 10⁸
一个数被拆成了三部分:
符号(正)
有效数字(2.99792458)
10 的几次方(8)
比如 5.5 这个十进制数,转成二进制是 101.1,可以写成:
+ 1.011 × 2²
同样的也被拆分成:
符号:正
有效数字:1.011
指数:2
二、IEEE754帧格式

简单来说
[ 符号 (1位) ] [ 指数部分 (若干位) ] [ 尾数部分 (若干位) ]
三、进制转换
例如:-6.125
第一步(将十进制转化成科学计数法):
(-6.125)10----->(110.001)2------->(1.10001 * 22)2第二步 (指数加偏置):
指数(2)+偏置值(127)=(129)10------>(1000 0001)2第三步(按照帧格式落座)
符号 指数 尾数(10001)
1 10000001 10001000000000000000000
尾数的隐含位:因为二进制有效数字的开头永远是 1(比如 1.011),所以这个 1 大家就不存了,直接把它默认带上,这样能多存一位精度。
指数部分用 8 位存储,范围是 0~255,去掉全 0 和全 1 两个特殊值,有效范围是 1~254。为了让正负指数都能表示,取中间值 127 作为零点——指数 = 实际指数 + 127。
边界情况
反向推导

四、IEEE的相关内容
1、在讨论之前:

↑ 数学中的整数是一个一个的, 想象绿色指针必须一次走一格
↑ 数学中的小数是连续的, 想象绿色指针可以无极调节, 想走到哪儿走到哪儿
↑ 计算机中存储的小数是一个一个的, 绿色指针必须一次走一格, 就像整数那样
这就引发了精度的问题, 比如上图中, 我们无法在计算机中存储0.3, 因为绿色指针只能一次走一格, 要么在0.234, 要么就到了0.468...
当然, 我们也可以增加计算机存储小数的精度, 或者说缩小点与点之间的间隔:

理解角度:
我们说过, 32位浮点数在内存中是这样表示的: 1位符号位, 8位指数位, 23位尾数位
事实上尾数位是24位, 因为在尾数位前还隐藏了一个整数部分1. 或 0.
仔细想一下, 浮点数内存的三个部分中:
符号位: 用于控制正负号
指数位: 控制指数, 其实也就是控制小数点的移动
尾数位: 其实真正控制精度的, 或者说真正记录状态的, 只有尾数位.
在24位尾数中,共包含2^24种状态, 或者说能精确记录2^24种不同的状态:
如果你准备记录2^24 + 1种状态, 那尾数就不够用了. 或者说就不能满足你对精度的需求了.
对于 float f = xxx; 其中xxx是个数值, 不管xxx你是用什么进制书写, 只要是使用32位浮点数作为容器, 就最多只能精确记录2^24种状态,即16,777,216个组合数
尾数 24 位二进制,能排出多少种不同的组合?
0000 0000 0000 0000 0000 0000 ← 第 1 种
0000 0000 0000 0000 0000 0001 ← 第 2 种
0000 0000 0000 0000 0000 0010 ← 第 3 种
...
1111 1111 1111 1111 1111 1111 ← 第 16,777,216 种
一共 2^24 = 16,777,216 种。
因此问题变成了:16,777,216 种状态,能撑起几位十进制精度?
尾数就是1-2之间,而 16,777,216是说明这之间有多少个可以确定的值
总结来说:超过 7 个数字的东西,丢给 float,后面的数字就不归我管了。、
由此引出舍入规则
2.舍入规则:
FPU 的默认模式:就近舍入,偶数优先
离上一个蓝点近 → 选上一个
离下一个蓝点近 → 选下一个
刚好在正中间 → 选末尾是偶数的那个
2.3 → 2 (离 2 近)
2.8 → 3 (离 3 近)
2.5 → 2 (正中间 2 是偶数,3 是奇数,选 2)
3.5 → 4 (正中间 3 是奇数,4 是偶数,选 4)
1.010 1 → 1.010 (1 开头,离上面近,向上舍)
1.010 0 110 → 1.010 (0 开头,离下面近,向下舍)
1.010 1 000 → 1.010 (正中间!末尾 010 是偶数,保持)
1.011 1 000 → 1.100 (正中间!末尾 011 是奇数,进位成 100 偶数)
快速判断
你要保留 3 位小数:
1.010 | 1 000
↑ ↑ ↑
保留位 guard sticky
(第一位)(后面所有位或起来)
guard=0 → 砍掉,啥也不干
guard=1 且后面有1 → 进 1
guard=1 且后面全0 → 看保留位末尾:偶不动,奇进 1
1.010 | 1001
│ └── sticky=1(后面有 1)
└──── guard=1
guard=1, sticky=1 → 进 1 → 存入1.011
到此我们就可以解释精度损失问题
精度损失
5.5 → 二进制 101.1 → 1.011 × 2²
0.1 → 二进制 0.000110011001100...(无限循环)
→ 尾数只有 23 位,后面全截断
→ 存进去的就不是真正的 0.1
→ 0.1 + 0.2 ≠ 0.3
详细计算:
(0.2)10---->(0.00110011001100110011001...)2--->1.1001 1001 1001 ... × 2^(-3)
(0.1)10---->(0.00110011001100110011001...)2--->1.1001 1001 1001 ... × 2^(-4)
(0.3)10---->(0.01001100110011001100110...)2--->1.0011 0011 0011 ... × 2^(-2)
如果在此处均由 float 存储
0.1
符号位->0
指数---> -4 + 127 = 123 = 0111 1011
尾数(含隐含位,共24位):1.1001 1001 1001 1001 100||||1 100 1 1...
因此保留小数点后23位,guard为1后面有1,进1
存储值为:
0 01111011 100110011001100110011010(最后一位补0)
0.2
符号位->0
指数---> -3 + 127 = 124 = 0111 1100
尾数(含隐含位,共24位):1.1001 1001 1001 1001 100||||1 100 1 1...
因此保留小数点后23位,guard为1后面有1,进1
存储值为:
0 01111011 100110011001100110011010(最后一位补0)
0.3
符号位->0
指数---> -2 + 127 = 125 = 0111 1101
尾数(含隐含位,共24位):1.1001 1001 1001 1001 100||||1 100 1 1...
因此保留小数点后23位,guard为1后面有1,进1
存储值为:
0 01111101 00110011001100110011010(最后一位补0)
因此:0.1+0.2 !=0.3
3、规格数,非规格数, ±infinity, NaN(特殊数):
根据IEEE754的规定, 按照尾数位隐藏的整数部分是 1. 还是0. 可以将浮点数划分为两类: normal number(规格数) 和 subnormal number(非规格数)
规格数:
就是尾数位隐藏的整数部分是1.的数, 这种数叫做normal number, 可以理解为"正常的数"
举例: 20.5在内存中表示为: 0 1000 0011 0100 1000 0000 0000 000
非规格数:
引入subnormal number这个概念, 是为了在浮点数下溢时, 可以逐位的损失精度, 以尽可能精确的表达0附近的极小数。
IEEE754规定: 如果将指数位全部填充为0, 则表示这个数是个subnormal number
特殊数:
当指数位全部被1填充, 即指数位表示的值为255时, 用于表示这个浮点数处在一种非正常数(non-number)的状态: 即这个数可能是±infinity或NaN.
注: Infinity和NaN是两个特殊数, 分别表示无穷和Not a Number.
所以: 当你看到类似于 11111111 ** 这样内存状态的数时, (即指数位全部为1的数), 就应该知道, 这是个non-number, 它用于表示特殊数.
1、首先, 如何区分这三种状态

其实这三种状态是通过指数部分区分的, 而且很容易区分.
以32位浮点数为例, 其内存状态分为3部分:
1位符号位 8位指数位 23位尾数位
其中, 如果8位指数位全为0, 就代表当前数是个非规格数. 或者说, 形如 00000000 ** 格式的数就是非规格数.
如果8位指数位全为1, 就代表当前数是个特殊数. 或者说, 形如 11111111 ** 格式的数就是特殊数.
如果8位指数不全为0, 也不全为1(也就是除去以上两种状态外, 剩下的所有状态), 这个数就是规格数.
随便几个例子: 10101100 **就是一个规格数
可见: 非规格数和特殊数是两种特殊状态, 规格数则是非常常见的状态
2 我们为什么要有这些特殊数?
规格数的局限性: 无法表示 0 和 及其靠近0 的数
32位浮点数的取值范围就是:203
我们可以看出,这个数字是始终无法等于零或者无限接近于0的
也就是说使用规格数时, 我们除了无法表示0, 也无法表示(0, 2^-167)之间的, 靠近0的极小数...
这个局限性将由非规格数解决.
2、 非规格数
从规格数看起,指数部分最小为(1)2,1-127,即(-126)10,也就是2的-126次幂,再也小不下去了
非规格数指数位全 0,但按规则它的实际指数固定为 −126,尾数前面隐含的是 0.。
也就是说,尾数可以取 0 ~ 0.999…9(二进制)
这就是非规格数的作用: 用于表示0和靠近0的数, 用于和规格数"珠联璧合", 形成一个完整的取值范围.
非规格数 "一点点逐渐变大, 最后其最大值平稳的衔接上规格数的最小值", 这种特性在IEEE754中被叫做逐渐溢出(gradual underflow)
所以, 即便把非规格数与规格数放在一起审视, ieee754浮点数表盘上的蓝点依旧是越靠近0越密集, 越靠近∞越稀疏

五、参考文章
IEEE 754 - 2019 浮点算数标准
IEEE754详解-CSDN博客
受限于个人水平,文中难免有理解不到位或笔误之处。恳请各位大佬不吝赐教,欢迎邮件交流:sharkey_z@163.com
