408答疑
0000 0000
(值为 0),最大数为 1111 1111
(值为 2 8 − 1 = 255 2^8 - 1 = 255 28−1=255),即表示范围为 0 ∼ 255 0\sim255 0∼255;而对于 8 位有符号整数(补码表示),最小数为 1000 0000
(值为 − 2 7 = − 128 -2^7 = -128 −27=−128),最大数为 0111 1111
(值为 2 7 − 1 = 127 2^7 - 1 = 127 27−1=127),即表示范围为 − 128 ∼ 127 -128\sim127 −128∼127。将符号数值化,并将符号位放在有效数字的前面,就组成了有符号整数。虽然前面介绍的原码、补码、反码和移码都可以用来表示有符号整数,但补码表示有其明显的优势。
补码表示法在计算机系统中被广泛采用,其主要优势包括:
0 的补码表示唯一:与原码和反码相比,0 的补码表示唯一。
运算规则简单:与原码和移码相比,补码运算规则比较简单,且符号位可以和数值位一起参加运算。
表示范围更广:与原码和反码相比,补码比原码和反码多表示一个最小负数。
表示范围:计算机中的有符号整数都用补码表示,所以 n n n 位有符号整数的表示范围是 − 2 n − 1 ∼ 2 n − 1 − 1 -2^{n-1} \sim 2^{n-1} - 1 −2n−1∼2n−1−1。
由于补码的这些优势,计算机系统中普遍采用补码来表示有符号整数,以简化运算规则并扩大表示范围。
C 语言变量之间的类型转换是统考中经常出现的题目,需要读者深入掌握这一内容。了解不同数据类型的表示范围和存储方式对于编写高效、可靠的程序至关重要。
C 语言中,有符号整数根据位数的不同,可分为短整型(short 或 short int,16 位)、整型(int,32 位)、长整型(long 或 long int,在 32 位机器中为 32 位,在 64 位机器中为 64 位)。
要定义无符号整数,只需在 short/int/long 之前加上关键字 unsigned,如无符号短整型(unsigned short 或 unsigned short int)、无符号整型(unsigned int)、无符号长整型(unsigned long 或 unsigned long int)。若不指定 signed/unsigned 时,则默认认为有符号整数。
字符型:字符型(char,8 位)是 C 语言中的一个特殊类型,默认按无符号整数解释。
存储方式:上述类型都是按补码形式存储的,只是有符号整数的最高位代表符号,而无符号整数的全部二进制位都表示数值,没有符号位,相当于数的绝对值,因此两者所表示的数据范围有所不同。
有符号整数表示范围:
无符号整数类型:
若不指定 signed/unsigned 时,则默认认为有符号整数。
强制类型转换:C 语言允许在不同的数据类型间做类型转换。强制类型转换的代码格式为 (TYPE b = (TYPE)a)
,强制类型转换后,返回一个具有 TYPE 类型的数值。
short 型转换到 unsigned short 型:将位数相同的 short 型强制转换为 unsigned short 型,两个变量对应的每位都是相同的,即强制类型转换的结果是保持二进制各位的位值不变,仅改变解释这些位的方式。
示例分析:
int main() {
short x = -4321;
unsigned short y = (unsigned short)x;
printf("x=%d, y=%u\n", x, y);
}
有符号整数 x
是一个负数,而无符号整数 y
只能表示一个正数,它们在计算机内部都占 16 位,y
的表示范围显然不包括 x
的值。
有符号整数 x = -4321
,无符号整数 y = 61215
。
输出的结果中,得到的 y
值似乎与原来的 x
没有一点关系。不过将这两个数转换为二进制表示时,我们就会发现其中的规律,如下表所示。
y 与 x 的对比
变量 | 值 | 位15 | 位14 | 位13 | 位12 | 位11 | 位10 | 位9 | 位8 | 位7 | 位6 | 位5 | 位4 | 位3 | 位2 | 位1 | 位0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
X | -4321 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 |
y | 61215 | 1 | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 |
unsigned short 型转换到 short 型:将无符号整数转换为有符号整数时,最高位解释为符号位,也可能发生数值的变化。
int main() {
unsigned short x = 65535;
short y = (short)x;
printf("x=%u, y=%d\n", x, y);
}
x
和 y
转换为二进制表示,变量 x
对应的二进制表示为 16 位全 1,若按有符号数的规则解释,则 16 位全 1 对应的真值是 -1。x = 65535
,无符号整数 y = -1
。若同时有无符号数和有符号数参与运算,则 C 语言标准规定按无符号数进行运算。
总结:
- 位数相同的有符号数转换为无符号数时,符号位解释为数值的一部分,负数转换为无符号数时数值将发生变化。
- 同理,位数相同的无符号数转换为有符号数时,最高位解释为符号位,也可能发生数值的变化。
大字长变量向小字长变量转换:
int main() {
int x = 165537, u = -34991; // int 型占用 4B
short y = (short)x, v = (short)u; // short 型占用 2B
printf("x=%d, y=%d\n", x, y);
printf("u=%d, v=%d\n", u, v);
}
x = 165537, y = -31071
u = -34991, v = 30545
0x000286a1
, 0x86a1
, 0xffff7751
, 0x7751
。小字长变量向大字长变量转换:
int main() {
short x = -4321;
int y = x;
unsigned short u = (unsigned short)x;
unsigned int v = u;
printf("x=%d, y=%d\n", x, y);
printf("u=%u, v=%u\n", u, v);
}
x = -4321, y = -4321
u = 61215, v = 61215
0xef1f
, 0xffffef1f
, 0xef1f
, 0x0000ef1f
。u
强制转换为 32 位无符号整数 v
时,高 16 位用 0 填充)。x
强制转换为 32 位有符号整数 y
时,因为 x
的符号位是 1,所以高 16 位用 1 填充)。char
型为 8 位无符号整数,其在转换为 int
型时高位补 0 即可。在有符号数和无符号数的转换中,若两个变量的字长不同,则分两种情况进行讨论:
- 若从小字长转换到大字长,则要先对原数字的高位部分进行扩展,若原数字是无符号整数,则进行零扩展;若原数字是有符号整数,则进行符号扩展。
- 若从大字长转换到小字长,则直接截取低位部分。也就是说,先进行字长的转换,再进行符号的转换。
浮点数表示法是一种在计算机中表示实数的方法,它通过将一个数表示为尾数(mantissa)和指数(exponent)的乘积来实现。这种表示方法允许在有限的位数内表示非常大或非常小的数,同时保持一定的精度。
浮点数表示法的优势:
例子:
定点数表示的局限性:
浮点数通常表示为:
N = ( − 1 ) S × M × R E N = (-1)^S \times M \times R^E N=(−1)S×M×RE
浮点数由符号、尾数和阶码三部分组成。
阶码的值反映浮点数的小数点的实际位置;阶码的位数反映浮点数的表示范围;尾数的位数反映浮点数的精度。
上溢和下溢的定义:
上溢和下溢的处理:
规格化操作的目的:为了在浮点数运算过程中尽可能多地保留有效数字的位数,使有效数字尽量占满尾数数位,必须在运算过程中对浮点数进行规格化操作。
规格化操作的定义:规格化操作是指通过调整一个非规格化浮点数的尾数和阶码的大小,使非零浮点数在尾数的最高数位上保证是一个有效值。
左规和右规:
基数为2的规格化尾数M:基数为2的原码规格化尾数 M M M 应满足 1 2 ≤ ∣ M ∣ < 1 \frac{1}{2} \leq |M| < 1 21≤∣M∣<1,形式如下:
基数不同对规格化形式的影响:基数不同,浮点数的规格化形式也不同。当浮点数尾数的基数为2时,原码规格化数的尾数最高位一定是1。当基数为4时,原码规格化数的尾数最高两位不全为0。
IEEE 754 标准浮点数的格式如下图所示。
IEEE 754 标准规定常用的浮点数格式有 32 位单精度浮点数(短浮点数、float 型)和 64 位双精度浮点数(长浮点数、double 型),其基数隐含为2,如下表所示。
类型 | 符号 s s s | 阶码 e e e | 尾数 f f f | 总位数 | 偏置值(十六进制) | 偏置值(十进制) |
---|---|---|---|---|---|---|
单精度 | 1 | 8 | 23 | 32 | 7FH | 127 |
双精度 | 1 | 11 | 52 | 64 | 3FFH | 1023 |
基数隐含为 2:尾数用原码表示。对于规格化的二进制浮点数,尾数的最高位总是 1,为了能使尾数表示一位有效位,将这个 1 隐藏,称为隐藏位,因此 23 位尾数实际表示了 24 位有效数字。
单精度与双精度浮点数都采用隐藏尾数最高位的方法,因此使浮点数的精度更高。
指数的偏置值:在 IEEE 754 标准中,指数用移码表示,但偏置值并不是通常 n n n 位移码所用的 2 n − 1 2^{n-1} 2n−1,而是 2 n − 1 − 1 2^{n-1} - 1 2n−1−1。因此:
阶码的存储:在存储浮点数阶码之前,偏置值要先加到阶码真值上。例如:
格式 | 最小值 | 最大值 |
---|---|---|
单精度 | e = 1 e = 1 e=1, f = 0 f = 0 f=0, 1.0 × 2 1 − 127 = 2 − 126 1.0\times2^{1-127}=2^{-126} 1.0×21−127=2−126 | e = 254 e = 254 e=254, f = .111... f = .111... f=.111..., 1.111...1 × 2 254 − 127 = 2 127 × ( 2 − 2 − 23 ) 1.111...1 \times 2^{254-127} = 2^{127} \times (2 - 2^{-23}) 1.111...1×2254−127=2127×(2−2−23) |
双精度 | e = 1 e = 1 e=1, f = 0 f = 0 f=0, 1.0 × 2 1 − 1023 = 2 − 1022 1.0\times2^{1-1023}=2^{-1022} 1.0×21−1023=2−1022 | e = 2046 e = 2046 e=2046, f = .1111... f = .1111... f=.1111..., 1.111...1 × 2 2046 − 1023 = 2 1023 × ( 2 − 2 − 52 ) 1.111...1 \times 2^{2046-1023} = 2^{1023} \times (2 - 2^{-52}) 1.111...1×22046−1023=21023×(2−2−52) |
对于 IEEE 754 格式的浮点数,阶码全为 0 或全为 1 时,有其特别的解释,如下表所示。
值的类型 | 符号(单精度 32 位) | 阶码(单精度 32 位) | 尾数(单精度 32 位) | 值(单精度 32 位) | 符号(双精度 64 位) | 阶码(双精度 64 位) | 尾数(双精度 64 位) | 值(双精度 64 位) |
---|---|---|---|---|---|---|---|---|
正零 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
负零 | 1 | 0 | 0 | -0 | 1 | 0 | 0 | -0 |
正无穷大 | 0 | 255(全1) | 0 | ∞ | 0 | 2047(全1) | 0 | ∞ |
负无穷大 | 1 | 255(全1) | 0 | -∞ | 1 | 2047(全1) | 0 | -∞ |
无定义数(非数) | 0 或 1 | 255(全1) | f ≠ 0 f \neq 0 f=0 | NaN | 0 或 1 | 2047(全1) | f ≠ 0 f \neq 0 f=0 | NaN |
非规格化正数 | 0 | 0 | f ≠ 0 f \neq 0 f=0 | 2 − 126 ( 0. f ) 2^{-126}(0.f) 2−126(0.f) | 0 | 0 | f ≠ 0 f \neq 0 f=0 | 2 − 1022 ( 0. f ) 2^{-1022}(0.f) 2−1022(0.f) |
非规格化负数 | 1 | 0 | f ≠ 0 f \neq 0 f=0 | − 2 − 126 ( 0. f ) -2^{-126}(0.f) −2−126(0.f) | 1 | 0 | f ≠ 0 f \neq 0 f=0 | − 2 − 1022 ( 0. f ) -2^{-1022}(0.f) −2−1022(0.f) |
将十进制数 -8.25 转换为 IEEE 754 单精度浮点数格式表示:
求 IEEE 754 单精度浮点数 C640 0000H 的值是多少?
先将C640 0000H 按二进制展开为 11000110010000000000000000000000 1100 0110 0100 0000 0000 0000 0000 0000 11000110010000000000000000000000。
因此,浮点数的值为 − 1.5 × 2 13 -1.5 \times 2^{13} −1.5×213。
不同类型数据转换后数值的变化:
float
型和 double
型分别对应于 IEEE 754 单精度浮点数和双精度浮点数。long double
型对应于扩展双精度浮点数,但其长度和格式随编译器和处理器类型的不同而有所差异。char→int→long→double
和 float→double
最为常见,转换过程中没有损失。类型转换原则:
long
型与 int
型一起运算时,需先将 int
型转换为 long
型,然后进行运算,结果为 long
型。float
型和 double
型一起运算,虽然两者同为浮点型,但精度不同,则仍需先将 float
型转换为 double
型后再进行运算,结果亦为 double
型。int 型和 float 型的精度和范围的分析:
int
型转换为 float
型时,虽然不会发生溢出,但 float
型尾数连隐藏位共 24 位,int
型为 32 位,当 int
型数转换成浮点数的尾数的有效位大于 24 位时,需舍入处理,影响精度。int
型或 float
型转换为 double
型时,因 double
型的有效位数更多,因此能保留精确值。double
型转换为 float
型时,因 float
型的表示范围更小,因此大数转换时可能发生溢出。此外,尾数有效位数变少,因此高精度数转换时会发生舍入。float
型或 double
型转换为 int
型时,因 int
型没有小数部分,因此数据会向 0 方向截断(仅保留整数部分),发生舍入。另外,因 int
型的表示范围更小,因此大数转换时可能溢出。在不同数据类型之间转换时,往往隐藏着一些不容易察觉的错误,编程时需要非常小心。
定点、浮点表示的区别:
- 数值的表示范围:若定点数和浮点数的字长相同,则浮点表示法所能表示的数值范围远大于定点表示法。
- 精度:对于字长相同的定点数和浮点数来说,浮点数虽然扩大了数的表示范围,但精度降低了。
- 数的运算:浮点数包括阶码和尾数两部分,运算时不仅要做尾数的运算,还要做阶码的运算,而且运算结果要求规格化,所以浮点运算比定点运算复杂。
- 溢出问题:在定点运算中,当运算结果超出数的表示范围时,发生溢出;在浮点运算中,运算结果超出尾数表示范围却不一定溢出,只有规格化后阶码超出所能表示的范围时,才发生溢出。
b站免费王道课后题讲解:
网课全程班: