IEEE 浮点数格式

LiYanrui posted @ Jun 24, 2008 11:59:12 PM in 程序设计 with tags IEEE Float , 18346 阅读

IEEE 754 标准规定了三种浮点数格式:单精度、双精度、扩展精度。《编程卓越之道》第一部的 4.2 节对这些浮点数格式已进行了详细的讲解,为了让读书笔记更像读书笔记,本文只道出个人的一些理解以及一些疑惑之处。

IEEE 浮点数标准的由来

话说 Intel 计划给最早的 8086 增加浮点运算单元 (FPU) 时,他们请来了最好的数值分析专家来为 8087 FPU 设计浮点数格式,这位专家接着又请来了该领域的另外两位专家,这三个人 (Kahn, coonan 与 Stone) 设计了 Intel 的浮点格式,即 KCS 浮点数标准。这个标准实在太出色了,因此 IEEE 组织将 KCS 选作为 IEEE 浮点数格式的基础,即 IEEE 标准 754。

单精度浮点数

IEEE 754 标准所定义的单精度浮点数的长度为 32 位,按位域可划分为:符号位、阶码位与尾数位,如下:

  31----------------------22---------------------------------------------------------0
  |                       |                                                          |
  X X X X    X X X X    X X X X    X X X X    X X X X    X X X X    X X X X    X X X X
  | |-------------------| |----------------------------------------------------------|
符号        阶码                                     尾数

符号位取 0 表示正数,取 1 表示负数。

阶码位是 8 位,这里有一点小门道需要注意,那就是$2^n$的指数 n 并不能直接当作阶码来处理,需要将其与 127 (0x7f) 相加才可得到 $2^n$ 的阶码表示。

尾数的位域长度在图示中是 23 位,但实际上却是 24 位,这个位是“不可见”的,其值固定为 1,这也就是说 IEEE 754 标准所定义的浮点数,其有效数字是介于 1 与 2 之间的小数。

可以尝试写一下 1.0 这个数的二进制单精度浮点格式,这有助于更好地理解单精度浮点数格式的位域分布。

1.0 的二进制单精度浮点格式:0 0111 1111 000 0000 0000 0000 0000 0000

值得注意的一个问题是:书上说之所以要将指数加上 127 来得到阶码,是为了简化浮点数的比较运算,这一点我没有体会出来。但是通过 127 这个偏移量 (移码),可以区分出指数的正负。阶码为 127 时表示指数为 0;阶码小于 127 时表示负指数;阶码大于 127 时表示正指数。

第二个值得思考的问题是:使用 24 位尾数,大概可以得到 $$6\frac{1}{2}$$ 个十进制数字的精度,其中的“半个”数字由 FPU 的好意而产生的一个随机数字,这个数字通常接近 5 (四舍五入?)。

第三个问题是我经常要碰到的:IEEE 754 标准所定义的单精度浮点数所表示的数的范围是多少?书上给出的答案是大约为 $2^{\pm128}$ 或者大约 $10^{\pm38}$。这个比较好理解,因为尾数的最大值是接近 2,而指数的范围是 [-127, 127],那么这个范围就可以表示为$2\times2^{\pm127}$

双精度浮点数

相对于单精度浮点数格式,双精度的阶码变为 11 位,移码变为为 1,023,尾数变为 53 位 (包含那个固定为 1 的隐含位)。这样,再加上符号位,双精度浮点数的长度为 64 位,提供了大约 $10^{\pm308}$ 的动态范围以及 $$14\frac{1}{2}$$ 个数字的精度。

扩展精度浮点数

为了追求更高的浮点运算精度,Intel 又搞出来扩展精度格式。扩展精度的浮点数长度为 80 个位,相对于双精度浮点数所多出来的 16 个位,有 12 位加入到尾数位中,有 4 位加入到阶码位中。

据说 Intel IA32 架构的的 FPU都是采用扩展精度浮点数进行运算的。当程序调入单、双精度浮点数时,FPU 将它们转为扩展精度,运算结束后再将结果转成 (四舍五入) 对应的单、双精度浮点数。

  • 无匹配
  • 无匹配
mycodenum 说:
2008年10月31日 16:48

呵呵
你是对的
对于上面第三点的FFFFFF80的出现是我的代码的问题,一个字节如果转换成16进制,那么只需要两个字节来表示,那么就是80,而我的代码把一个字节转换成16进制,要求输出8个字节,因为8的最高位为1,所以默认的扩展就是给前面多余的部分填充1,所以会多出来那么多的F,如果我把输出语句改成:
printf("%02X %02X %02X %02X\n", ch[0], ch[1], ch[2], ch[3]);
就对了。
这个就是我的错误,抱歉

mycodenum 说:
2008年11月01日 02:24

我写了一个简单的测试代码
但是感觉跟你写的不一样啊
或者是我的测试代码写的有问题
把我的代码贴出来与你讨论一下
#include <stdio.h>
#include <stdlib.h>

int main()
{
float i = 1.0;
char *ch = (char *)&i;
printf("%08X %08X %08X %08X\n", ch[0], ch[1], ch[2], ch[3]);
return 0;
}

我的输出为:
00000000 00000000 FFFFFF80 0000003F
而不是1.0 的二进制单精度浮点格式:
0 0111 1111 000 0000 0000 0000 0000 0000

Avatar_small
LiYanrui 说:
2008年11月01日 06:47

这个东西,我也是经常性的犯糊涂。下面咱们讨论一下。

1. 你的程序忽视了 x86 系列 CPU 的 little-endian 的字节

单精度浮点数 1.0 的二进制数若写成 16 进制,可以表示为 3F 80 00 00,在 little-endian 字节序中就变成了 00 00 80 3F。

2. 你的程序生成的字节序如果按照 little-endian 来看,应该是 3F 80 00 00。

3. 至于为什么会出现 FFFFFF80 而不是 00000080,这个我想不明白。或许与那个第 24 位的隐含位有关。

闪电 说:
2009年7月02日 01:50

#include <stdio.h>
void main()
float i= 5.2368;
unsigned char *ch;
ch = &i;
printf("%X %X %X %x\n", ch[0],ch[1],ch[2],ch[3]);

DOT 说:
2009年9月21日 18:58

只能说你在扯淡

Avatar_small
LiYanrui 说:
2009年9月22日 00:03

@DOT:

这篇破文可能不幸耽误了你一些时间,对不起~~


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter