往期地址:
- 操作系统系列一 —— 操作系统概述
- 操作系统系列二 —— 进程
- 操作系统系列三 —— 编译与链接关系
- 操作系统系列四 —— 栈与函数调用关系
- 操作系统系列五 —— 目标文件详解
- 操作系统系列六 —— 详细解释【静态链接】
- 操作系统系列七 —— 装载
- 操作系统系列八 ——动态链接
- 操作系统系列九 ——系统调用和API
本期主题:
浮点数存储和定点化逻辑
背景:
在工作中遇到一些寄存器的定点化问题,发现这个与浮点数的存储逻辑有一定相关性,因此把这块知识整理一下。
1.二进制小数
目前我们所用的浮点数的标准是IEEE-754标准。
IEEE是电气和电子工程师协会,是一个包括所有电子和计算机技术的专业团队。
需要理解浮点数,首先需要理解二进制小数。
首先我们可以看一下传统的十进制表示法,例如有一个数用十进制表示为:
d m d_m dm d m − 1 d_{m-1} dm−1 d m − 2 d_{m-2} dm−2 … d 1 d_1 d1 d 0 d_0 d0 . d − 1 d_{-1} d−1… d − n d_{-n} d−n
那么这个数的实际表示的值为:
d = ∑ i = − n m 1 0 i ∗ d i d = \sum_{i=-n}^m10^i*d_i d=∑i=−nm10i∗di
那么这个数如果用二进制表示的话,其实就是:
d = ∑ i = − n m 2 i ∗ d i d = \sum_{i=-n}^m2^i*d_i d=∑i=−nm2i∗di
例如, 101.1 1 2 101.11_2 101.112 实际上就是: 1 ∗ 2 2 + 1 ∗ 2 0 + 1 ∗ 2 − 1 + 1 ∗ 2 − 2 = 5 3 4 1*2^2+1*2^0+1*2^{-1}+1*2^{-2}=5\frac{3}{4} 1∗22+1∗20+1∗2−1+1∗2−2=543
从上面的公式也能知道,想用二进制表示一个小数,精度取决于用多少位来进行表示,例如
二进制表示值 | 精度 | 十进制 |
---|---|---|
0. 0 2 0.0_2 0.02 | 1 2 \frac{1}{2} 21 | 0. 0 10 0.0_{10} 0.010 |
0.0 1 2 0.01_2 0.012 | 1 4 \frac{1}{4} 41 | 0.2 5 10 0.25_{10} 0.2510 |
0.0100 1 2 0.01001_2 0.010012 | 1 32 \frac{1}{32} 321 | 9 32 = 0.2812 5 10 \frac{9}{32}=0.28125_{10} 329=0.2812510 |
2.浮点数存储
前面所描述的定点表示法,无法很有效地表示非常大的数字,例如表达式
5
∗
2
100
5*2^{100}
5∗2100,需要有100个0才能表达
因此IEEE754标准用:
V = ( − 1 ) s ∗ M ∗ 2 E V=(-1)^s*M*2^E V=(−1)s∗M∗2E 来表示一个数
其中:
- 符号(sign),s代表符号,s=1代表负数,s=0代表正数
- 尾数(significand),M是一个二进制小数,范围是1~2
- 阶码(exponent),E代表2的E次幂
因此我们可以将浮点数划分成3个字段,分别进行编码:
- 单独的符号s直接编码
- k位的阶码编码, e x p = e k − 1 e k − 2 . . . . e 0 exp=e_{k-1}e_{k-2}....e_0 exp=ek−1ek−2....e0
- n位小数字段编码, f r a c = f n − 1 f n − 2 . . . f 0 frac=f_{n-1}f_{n-2}...f_0 frac=fn−1fn−2...f0
在单精度浮点表达时,按照以下的格式进行表达:
根据指数位exp的内容,又能将浮点数的存储分为 规格化 、 非规格化存储 以及特殊值
规格化:
非规格化:
1.规格化值
当exp既不全为0,也不全为1时,这种情况下,阶码被解释为以bias形式表示的有符号整数,也就是说
阶码的值是 E = e - bias,其中e是无符号数,表示为 e k − 1 e k − 2 . . . e 1 e 0 e_{k-1}e_{k-2}...e_1e_0 ek−1ek−2...e1e0,bias是一个等于 2 k − 1 − 1 2^{k-1}-1 2k−1−1的值(单精度是127,双精度是1023)
小数字段frac被描述为小数值f, 0 ≤ f < 1 0\leq f< 1 0≤f<1,其二进制表示为 0. f n − 1 . . . f 1 f 0 0.f_{n-1}...f_1f_0 0.fn−1...f1f0
尾数 M=1+f, 1 ≤ M < 2 1\leq M< 2 1≤M<2,由于M的第一位必定是0,所以我们就不需要显式的表达它
规格化值的一个关键在于:
可以通过调整阶码E,使得尾数M的范围在有效范围内
例如从1.25->2.25,1.25用二进制表达为: 1.2 5 10 = 1.0 1 2 1.25_{10} = 1.01_2 1.2510=1.012,而 2.2 5 10 = 10.0 1 2 2.25_{10} = 10.01_2 2.2510=10.012,将阶码增加1变为 1.00 1 2 1.001_2 1.0012,因此1.25和2.25的差异在于2.25的阶码比1.25大1,并且小数部分,2.25是1.25的 1 2 \frac{1}{2} 21
2.非规格化值
阶码域全为0时,所表达的是非规格化形式,这种情况下
阶码值是 E= 1 - bias
尾数值是M = f,不包含隐式的开头1
3.示例
假设现在有一个8位浮点格式,其中阶码位为4位,小数位为3位
那么就有
b i a s = 2 4 − 1 − 1 = 7 bias = 2^{4-1}-1=7 bias=24−1−1=7
3.代码测试
实验目的:将输入浮点数的sign、exp、frac都打印出来,验证猜想
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
typedef struct _my_str_t
{
union
{
float val;
struct _my_union_str_t
{
uint32_t frac : 23;
uint32_t exp : 8;
uint32_t sign : 1;
} union_str_t;
};
} my_str_t;
int main(void)
{
my_str_t my_str = { 0 };
printf("Please input test float val :\r\n");
scanf("%f", (float *)&(my_str.val));
printf("sign is 0x%x, exp is 0x%x (%d), frac is 0x%x\r\n", my_str.union_str_t.sign, my_str.union_str_t.exp, my_str.union_str_t.exp, my_str.union_str_t.frac);
return 0;
}
测试结果:
结果也可以参考这里:
IEEE 754 converter