2.3.5 浮点数的表示与运算【2017统考真题】


好的,这是一道非常深刻的计算机组成原理综合题,它将C语言的数据类型、程序的编译执行与底层的数据表示(特别是int和float的区别)紧密地结合在一起。这道题的每一个小问都是一个经典的考点和“陷阱”。
我们来详细地解析它。
首先,附上题目原文:
题目原文
2.3.5 浮点数的表示与运算
(5)【2017统考真题】已知 f(n) = Σ(i=0 to n) 2ⁱ = 2ⁿ⁺¹ - 1 = 11…1B (n+1个1), 计算f(n)的C语言函数f1如下:
int f1(unsigned n){int sum = 1, power = 1;for(unsigned i=0; i <= n-1; i++){power *= 2;sum += power;}return sum;
}
将f1中的int都改为float,可得到计算f(n)的另一个函数f2。假设unsigned型和int型数据都占32位, float型数据采用IEEE754单精度标准。请回答下列问题:
- 当n=0时, f1会出现死循环,为什么?若将f1中的变量i和n都定义为
int型,则f1是否还会出现死循环?为什么? - f1(23)和f2(23)的返回值是否相等?机器数各是什么(用十六进制表示)?
- f1(24)和f2(24)的返回值分别为33554431和33554432.0,为什么不相等?
- f(31)=2³²-1,而f1(31)的返回值却为-1,为什么?若使f1(n)的返回值与f(n)相等,则最大的n是多少?
- f2(127)的机器数为7F800000H,对应的值是什么?若使f2(n)的结果不溢出,则最大的n是多少?若使f2(n)的结果精确(无舍入),则最大的n是多少?
综合解析
这道题的核心是深入辨析计算机中两种最基本的数据类型——整型和浮点型——在表示范围、精度和运算规则上的根本不同。
一、运用了什么知识点?
- C语言数据类型与运算:
unsigned int: 32位无符号整数,范围0到2³²-1。其运算会发生回绕 (Wrap-around),例如0U - 1U会得到2³²-1。int: 32位有符号整数,通常用二进制补码表示,范围-2³¹到2³¹-1。其运算会发生溢出 (Overflow)。
- IEEE 754 单精度浮点数标准:
- 表示格式: 1位符号位,8位阶码 (Exponent, 采用移码表示,偏置值为127),23位尾数 (Mantissa)。
- 精度限制: 尾数只有23位,加上隐藏的整数位1,总共能提供24位的二进制精度。任何需要超过24位二进制才能精确表示的整数,在存入
float时都会发生舍入 (Rounding),导致精度损失。 - 表示范围: 由8位阶码决定,远大于
int,但能表示的数是离散的。 - 特殊值: 阶码全1、尾数全0表示无穷大 (Infinity)。
二、考了什么?为什么这么考?
- 第1问:考察对
unsigned类型回绕特性的理解。这是一个非常经典的C语言“陷阱”,检验考生是否清楚无符号数和有符号数在边界条件下的运算差异。 - 第2、3问:通过对比
f(23)和f(24)的结果,考察对float类型精度限制的深刻理解。f(23)的结果2²⁴-1恰好是24位二进制数,是float能精确表示的整数的临界点之一。而f(24)的结果2²⁵-1是25位二进制数,超出了float的精度范围,必然导致舍入。 - 第4问:考察对 有符号
int类型溢出 的理解。f(31)的计算过程会超出int的最大正数范围,检验考生是否知道补码溢出的计算结果。 - 第5问:考察对
float类型表示范围和特殊值 的理解。检验考生是否能解码IEEE 754的特殊值(无穷大),并能根据阶码的范围计算出float能表示的最大数,从而反推出函数参数的极限。
为什么这么考? 因为这道题将高级语言程序的行为与底层硬件的数据表示紧密联系起来。它告诉我们,不能理所当然地认为计算机的运算和数学运算完全等价。程序员必须深刻理解数据在计算机内部的表示方式,才能预见并避免由类型限制(溢出、精度损失)带来的各种“诡异”的程序bug。
三、解题思路与详细分析 (为什么怎么样?)
问题1分析:死循环之谜
- 当n=0, i和n为
unsigned:- 循环条件是
i <= n-1。 - 当
n=0时,n-1实际上是(unsigned)0 - (unsigned)1。 - 对于无符号整数,这是一个下溢操作,结果会回绕到无符号数的最大值,即
2³²-1。 - 因此,循环条件变为
i <= 4294967295。 - 变量
i从0开始递增,它永远都会满足这个条件,从而导致死循环。
- 循环条件是
- 当i和n为
int:- 循环条件仍然是
i <= n-1。 - 当
n=0时,n-1是(int)0 - (int)1 = -1。 - 变量
i从0开始。 - 第一次判断循环条件时,是
0 <= -1,这个条件为假。 - 因此,循环体一次都不会执行,函数直接返回
sum的初始值1。 - 结论: 不会出现死循环。
- 循环条件仍然是
问题2分析:f1(23) vs f2(23)——精度的临界点
- f(23) 的数学值是
2²⁴-1。这是一个由24个1组成的二进制数。 - f1(23) (int):
2²⁴-1 = 16777215,这个值远小于int的最大值 (2³¹-1),所以可以精确计算和存储。- 其机器数(二进制补码)就是其原码,十六进制为
00FFFFFFH。
- f2(23) (float):
- 要精确表示
2²⁴-1,需要24位的二进制精度。 float的尾数是23位,但由于规格化数有一个隐藏的前导1,所以它正好能提供24位精度。2²⁴-1恰好是float可以精确表示的最大整数之一。- 因此,
f1(23)和f2(23)的返回值相等。 - 其机器数(IEEE 754):
- 值:
(2²⁴-1) = 1.11...1B * 2²³(小数点后23个1) - 符号位:
0(正数) - 阶码:
23 + 127 = 150(二进制10010110) - 尾数: 23个1
- 组合:
0 10010110 11111111111111111111111-> 十六进制4B7FFFFFH。
- 值:
- 要精确表示
问题3分析:f1(24) vs f2(24)——精度的丢失
- f(24) 的数学值是
2²⁵-1。这是一个由25个1组成的二进制数。 - f1(24) (int):
2²⁵-1 = 33554431,仍在int范围内,计算精确。 - f2(24) (float):
- 要精确表示
2²⁵-1,需要25位的二进制精度。 float只能提供24位精度,因此无法精确表示,必须进行舍入。2²⁵-1这个数,离它最近的两个可以用float精确表示的数是2²⁵和2²⁵-2。它正好在中间。根据IEEE 754的“舍入到最近偶数”规则,它会被舍入到2²⁵(因为2^25的尾数全0,是偶数)。- 因此,
f2(24)的返回值实际上是2²⁵ = 33554432.0。 - 结论: 因为
float的精度不足以表示25位有效数字,导致舍入误差,所以两者不相等。
- 要精确表示
问题4分析:f1(31)——整型的溢出
- 为什么返回-1:
f(30) = 2³¹-1,这正好是int能表示的最大正数。此时sum的值是0x7FFFFFFF。- 当循环到
i=30时,power的值是2³⁰。sum的值是f(29)。 - 我们直接看最后一步,当循环到
i=30结束时,sum的值为f(30)=2^31-1。此时i变成31,i<=n-1(31<=30)不成立,循环结束。等等,题目是f(31)。 - 当
n=31时,循环条件i<=30。当i=30时,power = 2^30,sum累加到f(30)=2^31-1。 - 我们来看
f(31),其循环i<=30。 - 当
i从0到29循环完,sum=f(29) = 2³⁰-1。 - 当
i=30,power = 2³⁰。sum += power->sum = (2³⁰-1) + 2³⁰ = 2*2³⁰ - 1 = 2³¹-1。 - 此时
sum的值是0x7FFFFFFF。 - 等等,f1(31)的返回值是-1,
power在i=31时会溢出。 - f(31)的循环是
i <= 30。当i=31时,power是2^31,在int中表示为-2^31。sum是2^31-1。sum += power变成(2^31-1) + (-2^31) = -1。 - 更正:
f1(31),n=31,循环条件i<=30。在循环最后一步i=30时,power已经是2^31,此时power溢出为-2^31。sum是f(29)=2^30-1。sum+power = 2^30-1 - 2^31 = -2^30-1。这个分析复杂了。 - **最简单的分析:
f(31) = 2³²-1。在32位二进制补码中,2³²-1的机器数是32个1,即 `FFFF
