
CS-MEDIUM-02 浮点数
说明
本题目旨在帮助大家理解浮点数在计算机中的表示原理,推荐阅读《深入理解计算机系统》(CSAPP) 第 2.4 节 "浮点数"
Step 1.认识浮点数
请编写并运行如下代码,观察输出结果:
#include <stdio.h>
int main() {
float a = 0.1;
printf("%.20f\n", a);
return 0;
}#include <stdio.h>
int main() {
float a = 0.1;
printf("%.20f\n", a);
return 0;
}在数学上,0.1 就是 0.100000...,所以你可能预期会输出 0.10000000000000000000。
但实际运行,你却会看到一个略微不同的值。
首先我们知道计算机底层使用二进制表示所有数据,包括浮点数。但并不是所有的十进制小数都能被二进制精确表示。
所以你能解释为什么 0.1 并没有被精确表示出来吗?
误差的根本原因,还与浮点数的存储结构有关。根据 IEEE 754 浮点数标准,一个浮点数由三部分构成:
符号位(sign):1 位,决定正负。
阶码(exponent):表示数量级,类似科学计数法中的指数部分。
尾数(significand):表示有效数字部分。
例如, 在浮点存储中会被拆分成:
- 符号位:1(负数)
- 阶码:5(会加偏置存储)
- 尾数:1.234(存储时去掉首位 1 只保留小数部分)
请查阅相关资料并回答:
在 IEEE 754 标准中,float 与 double 分别使用多少位来表示这三部分?
在存储阶码时,IEEE 754 并不直接保存正负值,而是把它加上一个**偏置(Bias)**后存为无符号整数。
例如:在一个float类型中,若阶码实际值为5,但在计算机中存储的却是:
请回答:偏置值是如何计算的?这样做的意义是什么?
当浮点数表示的数值非常接近 0 时,规格化表示的阶码已经到最小了,不能再减。这时会用非规格化数
请查阅相关资料并回答:
非规格化数的特点是什么?
此外,还有两个特殊阶码模式:
- 阶码全为 1 且尾数全为 0
- 阶码全为 1 且尾数非 0
回答这两个特殊阶码模式分别指的什么?
注意:该任务仅为导读,要学习浮点数请阅读相关书籍或观看相关视频。
要求:在markdown中回答上述中出现的问题
Step 2.表示浮点数
假设一个基于IEEE浮点格式的8位浮点表示,有1个符号位、 4个阶码位(k=4)和3个小数位(n=3)。阶码偏置值是。下表中列举了这个8位浮点表示的部分非负取值。使用下面的条件,填写表格中的空白项:
e: 假定阶码字段是一个无符号整数所表示的值。
E: 偏置之后的阶码值。
: 阶码的权重。
f: 小数值。
M: 尾数的值。
: 该数小数值(无需化为最简)。
十进制:该数的十进制表示(不超过六位小数)。
注意:、f、M、的值,要么是整数(如果可能的话),要么是形如的小数,这里y是2的幂。标注为“—”的条目不用填。
| 位表示 | e | E | f | M | 十进制 | ||
|---|---|---|---|---|---|---|---|
| 0 0000 000 | 0 | -6 | 0.0 | ||||
| 0 0000 001 | |||||||
| 0 0000 011 | |||||||
| 0 0000 111 | |||||||
| 0 0001 000 | 1 | -6 | 0.015625 | ||||
| 0 0001 001 | |||||||
| 0 0111 000 | |||||||
| 0 0111 110 | |||||||
| 0 1110 111 | |||||||
| 0 1111 000 | — | — | — | — | — | — | — |
思考:
- 在该8位浮点表示中,最小的非规格化数、最大的非规格化数、最小的规格化数、最大的规格化数、无穷大的位表示分别是什么?
- 对于非规格化形式,阶码值为什么是而不是简单的?
提交方式:于markdown中提交完整的表格,并回答思考题。
Step 3
要求:仿照IEEE 754 标准,实现一个C程序,要求输入5位浮点字符串,输出对应的十进制值。
其中5位浮点数格式如下:
- 符号位:0表示正数,1表示负数
- 阶码:2位
- 尾数:2位
- 偏置值:
输入输出示例:
| 输入 | 输出 |
|---|---|
| 00000 | 0 |
| 00010 | 0.5 |
| 10101 | -1.25 |
| 01100 | +Inf |
| 01111 | NaN |
提交方式:于markdown中提交相关代码和运行截图。
Step 4.浮点数的简单运算
相信在完成上述任务之后你对浮点数已经有了比较深刻的认识,现在让我们来看看浮点数的加减法是如何实现的。
浮点数的加减运算一般由以下五个步骤完成:对阶、尾数运算、规格化、舍入处理、溢出判断
1.对阶
所谓对阶是指将两个进行运算的浮点数的阶码对齐的操作。类比平常我们用到的带阶数的加减法,对阶的目的是为使两个浮点数的尾数能够进行加减运算。在对阶的过程中有两种方式,大阶向小阶看齐,小阶向大阶看齐。但实际上我们是用小阶向大阶看齐的方式。请思考其中的原因。
2.尾数运算
尾数运算就是进行完成对阶后的尾数相加减。
3.规格化
对于IEEE754标准的浮点数来说,由于在进行上述两个定点小数的尾数相加减运算后,尾数有可能是非规格化形式,为此必须进行规格化操作。规格化操作包括左规和右规两种情况。
左规操作:将尾数左移,同时阶码减值,直至尾数成为1.M的形式。例如,浮点数0.001111是非规格化的形式,需进行左规操作,将其尾数左移3位,同时阶码减3,就变成1.110000规格化形式了。
右规操作:将尾数右移1位,同时阶码增1,便成为规格化的形式了。要注意的是,右规操作只需将尾数右移一位即可,这种情况出现在尾数的最高位(小数点前一位)运算时出现了进位,使尾数成为10.xxxx或11.xxxx的形式。例如,10.001100右规一位后便成为1.0001100的规格化形式了。
4.舍入处理
浮点运算在对阶或右规时,尾数需要右移,被右移出去的位会被丢掉,从而造成运算结果精度的损失。为了减少这种精度损失,可以将一定位数的移出位先保留起来,称为保护位,在规格化后用于舍入处理。IEEE754标准列出了四种可选的舍入处理方法,这里请自行查阅不详细列出。
5.溢出判断
与定点数运算不同的是,浮点数的溢出是以其运算结果的阶码的值是否产生溢出来判断的。若阶码的值超过了阶码所能表示的最大正数,则为上溢,进一步,若此时浮点数为正数,则为正上溢,记为+∞,若浮点数为负数,则为负上溢,记为-∞;若阶码的值超过了阶码所能表示的最小负数,则为下溢,进一步,若此时浮点数为正数,则为正下溢,若浮点数为负数,则为负下溢。正下溢和负下溢都作为0处理。
下面给出具体运算过程示例(以32位系统为例):
float a = 0.3; b = 1.6;
a = 0011 1110 1001 1001 1001 1001 1001 1010 Sa=0 Ea=011 1110 1 Ma=1.001 1001 1001 1001 1001 1010
b = 0011 1111 1100 1100 1100 1100 1100 1101 Sb=0 Eb=011 1111 1 Mb=1.100 1100 1100 1100 1100 1101
a + b= ?
第一步:对阶
∵ Ea < Eb Eb - Ea = 2
∴ Ma要调整为 0.0 1001 1001 1001 1001 1001 10 10
E = 011 1111 1
第二步:尾数运算
0.01001100110011001100110
+1.10011001100110011001101
1.11100110011001100110011
第三步:规格化
1.11100110011001100110011已经是个规格化数据了
第四步:舍入处理
由于在对阶时,Ma有右移,且第一次最高为1,第二次为0,所以按"0舍1入",尾数运算结果调整为 1.11100110011001100110100
第五步:溢出判断
没有溢出,阶码不调整,所以最后的结果为
a+b = 0 01111111 11100110011001100110100 = 0011 1111 1111 0011 0011 0011 0011 0100
转为10进制
a+b = 1.90000010
按照任务二的八位浮点系统,请设计一个c程序,要求为输入两个八位二进制浮点数(中间用空格隔开),然后对两个浮点数进行加法操作,只完成对阶,尾数运算和规格化的操作,输出规格化后的二进制表达式(不考虑溢出,若需进行舍入采取向零舍入)。为简化实现,本题只考虑非负的规格化数,忽略非规格化数和其它特殊情况。
输入输出示例
| 输入 | 输出 |
|---|---|
| 01001011 01001100 | 01010011 |
| 01000110 01001101 | 01010010 |
| 01001000 01000111 | 01001111 |
| 01000111 01000101 | 01001110 |
提交方式:于markdown中提交相关代码和运行截图。
本题提交方式
出题人联系方式
逐影 QQ:1329007256