计算机中的浮点数表示

本文介绍小数在计算机中的表现形式,基于IEEE标准。

浮点数表示

浮点数(floating point):二进制小数点不固定的表达数的记数法。

在许多编程语言中,都提供了一种或者多种浮点数类型。例如 C 语言中提供了双精度浮点类型double,单精度浮点类型float,这两种数据类型都采用浮点数来表示。浮点数 \(\pm m \times n^e\) 是指由符号 (sign) 、指数 (exponent) 、尾数 (fraction) 、基数 (cardinal number) 四部分来表示的小数,数被表示为二进制小数点左边只有一位非零数的形式。在计算机中使用二进制表示数据,因此基数为2。因此计算机中的浮点数可以分成三个部分,符号,尾数和指数。

  • 双精度浮点数(64位)
1位 8位 52位
符号部分 指数部分 尾数部分
  • 单精度浮点数(32位)
1位 8位 23位
符号部分 指数部分 尾数部分

下面以单精度浮点数为例,双精度浮点数表示类似。

符号部分

符号部分的表示使用1-bit ,和二进制整数的符号位表示法类似,符号位在最高位,0为正数,1为负数。

将符号放在最高为,可以快速测试出大于、小于、等于0的情况。

尾数部分

尾数:位于小数点的位数字段,值在0和1之间。

单精度浮点数中尾数长度是23个二进制数字。尾数部分是被正则化的,即小数点左边有且仅有一位非0数字,在二进制表示方式下,非0即1,因此这个数字只能为1,IEEE 754隐藏了规格化二进制数的前导位1,这样,23-bit 的尾数部分实际上可以表示 24-bit 的精度。

指数部分

指数:位于浮点数的指数字段,表示小数点的位置。

但制数部分如果为负数,使用补码,可能会使得一个负指数显得像是一个大数,因此指数部分使用的是 EXCESS 系统,将中间数字设置为0,负数不需要负号来表示,将最小的负指数表示为\(00\cdots00_2\) ,最大的正指数表示为\(11\cdots11_2\),这种记数法称为带偏阶的记数法(biased notation),要从带偏阶的指数中减去偏阶,才能获得真实的值。8-bit 的指数字段可以表示 256 个不同的指数值,但 0000 0000 和 1111 1111 有特殊含义,稍后再提。剩下的 254 个数值,即 1~254,实际的指数值等于该无符号整数减单精度偏阶127所得的值。

这样单精度浮点数表示为: \[ N =(-1)^s \times (1+Fraction) \times 2 ^{Exponent-Bias},\quad 1 \le Exponent \le 254, Bias = 127 \]

0000 0000 的情况

如果指数字段是0000 0000,则代表指数值-126,且尾数中小数点左边默认数字是0(而不是1),这种情况下,浮点数值为: \[ N = (-1)^s \times 0.尾数 \times 2 ^{-126} \] 若指数与尾数均为0,则根据符号位表示 \(\pm 0​\)

但为什么不是用$1.尾数 ^{-127} $呢?考虑以下情况,\(1.11\cdots11\times 2^{-127}\)\(1.11\cdots 10 \times 2 ^{-127}\) 两个数相减的情况,差为\(0.00\cdots 01 \times 2 ^{-127}\) ,这个差值将无法使用这种表示法来表示。为了解决这种情况,在表示比\(1.00\cdots 00\times 2 ^{-126}\)小的数时,我们用0取代被我们忽略的默认小数点左边的1。

1111 1111 的情况

  • 尾数为0 :根据符号位表示 \(\pm \infty\)
  • 尾数非0:表示 NaN 非数字, (Not a Number)

IEEE 754 浮点数的编码

单精度 双精度 表示对象
指数 尾数 指数 尾数
0 0 0 0 0
0 非0 0 非0 \(\pm\) 非规格化数
1-254 任何值 1-2046 任何值 \(\pm\)浮点数
255 0 2047 0 \(\pm \infty\)
255 非0 2047 非0 NaN

十进制小数转IEEE浮点数

方法

  1. 将十进制小数表示为二进制小数
  2. 正则化处理
  3. 根据符号确定符号位
  4. 计算指数部分
  5. 计算尾数部分

举例

下面我们将\(-6 \frac{5}{8}\) 表示为IEEE浮点数:

  1. 首先将\(-6 \frac{5}{8}\) 表示为二进制数: \[ -6 \frac{5}{8} = -(1\times 2^2 + 1 \times 2^1 + 0 \times 2^0 + 1\times 2^{-1} + 0 \times 2^{-2} + 1\times 2^{-3} \]

  2. 正则化处理得到 \(-1.10101 \times 2 ^2\)

  3. 负数,符号位为1

  4. 计算指数部分:实际指数为2,加上127得到129,二进制表示指数部分为1000 0001

  5. 计算尾数部分,忽略小数点左边的1,得到尾数部分为 1010 1000 0000 0000 0000 000 。

C语言实现 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* 用于确认单精度浮点数表示方法的C语言程序(摘自《程序是怎样跑起来的》),做了一点小改动 */

#include <stdio.h>
#include <string.h>

int main()
{
float data;
unsigned long buff;
int i;
char s[34];

scanf("%f", &data);

// 把数据复制到 4 字节长度的整数变量 buff 中以逐个提取出每一位
memcpy(&buff, &data, 4);

// 逐一提取出每一位
for (i = 33; i >= 0; i--) {
if (i == 1 || i == 10)
// 加入破折号来区分符号部分、指数部分和尾数部分
s[i] = '-';
else {
if (buff % 2 == 1)
s[i] = '1';
else
s[i] = '0';
buff /= 2;
}
}
s[33] = '\0';

printf("%s\n", s);

return 0;
}

IEEE 浮点数转十进制数

方法

  1. 由第一位判断符号
  2. 由第二位至第九位计算十进制值减去127得到指数值
  3. 后23位为尾数值

举例

将浮点数 0 1000 0011 0010 1000 0000 0000 0000 000 转为十进制数:

  1. 符号位为0,该数为正数
  2. 指数字段 1000 0011 转为十进制数为 131,得到实际指数值为4
  3. 尾数部分为 0010 1000 0000 0000 0000 000,在小数点左边补1,得到1.0010 1
  4. 因为实际指数值为4,故将小数点右移4位,得到10010.1
  5. 转为十进制得到18.5

参考资料


  1. Source Code - Gits↩︎