2.2 整数表示
C语言整数取值范围
| C 数据类型 |
32 位系统取值范围 |
64 位系统取值范围 |
[signed] char |
−27-2^{7}−27 ~ 27−12^{7} - 127−1 |
−27-2^{7}−27 ~ 27−12^{7} - 127−1 |
unsigned char |
000 ~ 28−12^{8} - 128−1 |
000 ~ 28−12^{8} - 128−1 |
short |
−215-2^{15}−215 ~ 215−12^{15} - 1215−1 |
−215-2^{15}−215 ~ 215−12^{15} - 1215−1 |
unsigned short |
000 ~ 216−12^{16} - 1216−1 |
000 ~ 216−12^{16} - 1216−1 |
int |
−231-2^{31}−231 ~ 231−12^{31} - 1231−1 |
−231-2^{31}−231 ~ 231−12^{31} - 1231−1 |
unsigned int |
000 ~ 232−12^{32} - 1232−1 |
000 ~ 232−12^{32} - 1232−1 |
long |
−231-2^{31}−231 ~ 231−12^{31} - 1231−1 |
−263-2^{63}−263 ~ 263−12^{63} - 1263−1 |
unsigned long |
000 ~ 232−12^{32} - 1232−1 |
000 ~ 264−12^{64} - 1264−1 |
long long |
−263-2^{63}−263 ~ 263−12^{63} - 1263−1 |
−263-2^{63}−263 ~ 263−12^{63} - 1263−1 |
unsigned long long |
000 ~ 264−12^{64} - 1264−1 |
000 ~ 264−12^{64} - 1264−1 |
char = 8 位
short = 16 位
int = 32 位
long long = 64 位
取值范围不是对称的,负数的范围比整 数的范围大1。
C 标准 (如 ISO/IEC 9899)不规定具体位数 ,而是规定每种整型类型必须至少支持的最小取值范围。这是为了保证可移植性。
| C 数据类型 |
最小值 |
最大值 |
| [signed] char |
-127 (−27+1-2^7 + 1−27+1) |
127 (27−12^7 - 127−1) |
| unsigned char |
0 |
255 (28−12^8 - 128−1) |
| short |
-32 767 (−215+1-2^{15} + 1−215+1) |
32 767 (215−12^{15} - 1215−1) |
| unsigned short |
0 |
65 535 (216−12^{16} - 1216−1) |
| int |
-32 767 (−215+1-2^{15} + 1−215+1) |
32 767 (215−12^{15} - 1215−1) |
| unsigned |
0 |
65 535 (216−12^{16} - 1216−1) |
| long |
-2 147 483 647 (−231+1-2^{31} + 1−231+1) |
2 147 483 647 (231−12^{31} - 1231−1) |
| unsigned long |
0 |
4 294 967 295 (232−12^{32} - 1232−1) |
| int32_t |
-2 147 483 648 (−231-2^{31}−231) |
2 147 483 647 (231−12^{31} - 1231−1) |
| uint32_t |
0 |
4 294 967 295 (232−12^{32} - 1232−1) |
| int64_t |
-9 223 372 036 854 775 808 (−263-2^{63}−263) |
9 223 372 036 854 775 807 (263−12^{63} - 1263−1) |
| uint64_t |
0 |
18 446 744 073 709 551 615 (264−12^{64} - 1264−1) |
!NOTE
C 和 C++ 都支持有符号(默认)和无符号数。Java 只支持有符号数。
无符号整数
对于一个 www 位的整数数据类型,位向量表示为 x⃗=[xw−1,xw−2,...,x0]\vec{x} = [x_{w-1}, x_{w-2}, \dots, x_0]x =[xw−1,xw−2,...,x0]。将位向量看作二进制表示的数,即可获得其无符号表示。
编码的定义
使用函数 B2UwB2U_wB2Uw (Binary to Unsigned) 来表示:
B2U_w(\vec{x}) \doteq \sum_{i=0}^{w-1} x_i 2^i
* **xix_ixi** :位向量中的第 iii 位,取值为 0 或 1。
* **2i2\^i2i** :第 iii 位对应的权重(由 2 的幂组成)。
*** ** * ** ***
**映射示例**
通过将每个值为 1 的位对应的权重相加得到最终的十进制数值:
| 位向量 \[x3,x2,x1,x0\]\[x_3, x_2, x_1, x_0\]\[x3,x2,x1,x0\] | 计算过程 | 十进制结果 |
|:---------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------|:-------|
| \[0001\]\[0001\]\[0001\] | 0⋅23+0⋅22+0⋅21+1⋅20=0+0+0+10\\cdot2\^3 + 0\\cdot2\^2 + 0\\cdot2\^1 + 1\\cdot2\^0 = 0+0+0+10⋅23+0⋅22+0⋅21+1⋅20=0+0+0+1 | **1** |
| \[0101\]\[0101\]\[0101\] | 0⋅23+1⋅22+0⋅21+1⋅20=0+4+0+10\\cdot2\^3 + 1\\cdot2\^2 + 0\\cdot2\^1 + 1\\cdot2\^0 = 0+4+0+10⋅23+1⋅22+0⋅21+1⋅20=0+4+0+1 | **5** |
| \[1011\]\[1011\]\[1011\] | 1⋅23+0⋅22+1⋅21+1⋅20=8+0+2+11\\cdot2\^3 + 0\\cdot2\^2 + 1\\cdot2\^1 + 1\\cdot2\^0 = 8+0+2+11⋅23+0⋅22+1⋅21+1⋅20=8+0+2+1 | **11** |
| \[1111\]\[1111\]\[1111\] | 1⋅23+1⋅22+1⋅21+1⋅20=8+4+2+11\\cdot2\^3 + 1\\cdot2\^2 + 1\\cdot2\^1 + 1\\cdot2\^0 = 8+4+2+11⋅23+1⋅22+1⋅21+1⋅20=8+4+2+1 | **15** |
*** ** * ** ***
**取值范围**
无符号数能够表示的数值范围由位数 www 决定:
* **最小值 (UMinwUMin_wUMinw)** :位向量为 \[00...0\]\[00\\dots0\]\[00...0\] 时,对应整数 **0**。
* **最大值 (UMaxwUMax_wUMaxw)** :位向量为 \[11...1\]\[11\\dots1\]\[11...1\] 时,对应整数 2w−12\^w - 12w−1。
* 公式:UMaxw≐∑i=0w−12i=2w−1UMax_w \\doteq \\sum_{i=0}\^{w-1} 2\^i = 2\^w - 1UMaxw≐∑i=0w−12i=2w−1
* 例如:4位最大值为 24−1=152\^4 - 1 = 1524−1=15。
*** ** * ** ***
**编码的唯一性 (双射)**
函数 B2UwB2U_wB2Uw 是一个**双射 (Bijection)**。
* **含义** :
1. 每个长度为 www 的位向量都有唯一一个整数值与之对应。
2. 反之,在 0∼2w−10 \\sim 2\^w-10∼2w−1 范围内的每一个整数,都有唯一一个长度为 www 的位模式与之对应。
* **反函数** :U2BwU2B_wU2Bw (Unsigned to Binary),即将十进制无符号数转换回二进制位模式。
#### 补码
补码是计算机中表示有符号整数(Signed Integers)最常见的形式。其核心逻辑是将字(word)的**最高有效位(MSB)**解释为**负权重(Negative Weight)**。
对于一个 www 位的位向量 x⃗=\[xw−1,xw−2,...,x0\]\\vec{x} = \[x_{w-1}, x_{w-2}, \\dots, x_0\]x =\[xw−1,xw−2,...,x0\],其补码值通过函数 B2TwB2T_wB2Tw(Binary to Two's-complement)计算:
B2Tw(x⃗)≐−xw−1⋅2w−1+∑i=0w−2xi⋅2iB2T_w(\\vec{x}) \\doteq -x_{w-1} \\cdot 2\^{w-1} + \\sum_{i=0}\^{w-2} x_i \\cdot 2\^iB2Tw(x )≐−xw−1⋅2w−1+i=0∑w−2xi⋅2i
* **符号位** :最高位 xw−1x_{w-1}xw−1 被称为符号位。
* 当 xw−1=1x_{w-1} = 1xw−1=1 时,表示值为负(权重为 −2w−1-2\^{w-1}−2w−1)。
* 当 xw−1=0x_{w-1} = 0xw−1=0 时,表示值为非负。
**计算示例 (w=4w=4w=4):**
| 位模式 | 权重分解计算 | 结果值 |
|:---------|:----------------------------------------------------------------------------------------------------------------------------|:----|
| `[0001]` | −0⋅23+0⋅22+0⋅21+1⋅20=1-0\\cdot2\^3 + 0\\cdot2\^2 + 0\\cdot2\^1 + 1\\cdot2\^0 = 1−0⋅23+0⋅22+0⋅21+1⋅20=1 | 1 |
| `[0101]` | −0⋅23+1⋅22+0⋅21+1⋅20=4+1-0\\cdot2\^3 + 1\\cdot2\^2 + 0\\cdot2\^1 + 1\\cdot2\^0 = 4+1−0⋅23+1⋅22+0⋅21+1⋅20=4+1 | 5 |
| `[1011]` | −1⋅23+0⋅22+1⋅21+1⋅20=−8+2+1-1\\cdot2\^3 + 0\\cdot2\^2 + 1\\cdot2\^1 + 1\\cdot2\^0 = -8+2+1−1⋅23+0⋅22+1⋅21+1⋅20=−8+2+1 | -5 |
| `[1111]` | −1⋅23+1⋅22+1⋅21+1⋅20=−8+4+2+1-1\\cdot2\^3 + 1\\cdot2\^2 + 1\\cdot2\^1 + 1\\cdot2\^0 = -8+4+2+1−1⋅23+1⋅22+1⋅21+1⋅20=−8+4+2+1 | -1 |
由于**最高位的负权重特性**,补码表示的数值范围具有不对称性。
* **最小值 (TMinwTMin_wTMinw)** :位向量为 `[10...0]`
* 公式:TMinw=−2w−1TMin_w = -2\^{w-1}TMinw=−2w−1
* **最大值 (TMaxwTMax_wTMaxw)** :位向量为 `[01...1]`
* 公式:TMaxw=∑i=0w−22i=2w−1−1TMax_w = \\sum_{i=0}\^{w-2} 2\^i = 2\^{w-1} - 1TMaxw=∑i=0w−22i=2w−1−1
**以 w=4w=4w=4 为例:**
* TMin4=−23=−8TMin_4 = -2\^3 = -8TMin4=−23=−8
* TMax4=23−1=7TMax_4 = 2\^3 - 1 = 7TMax4=23−1=7
*** ** * ** ***
函数 B2TwB2T_wB2Tw 是一个**双射**。这意味着:
1. 每个长度为 www 的位模式对应唯一一个 TMinTMinTMin 到 TMaxTMaxTMax 之间的数字。
2. 反之,在该范围内的每个数字也都有唯一的 www 位补码编码(由反函数 T2BwT2B_wT2Bw 定义)。
这一特性保证了有符号数在计算机底层表示的确定性,与无符号数原理相似。
> \[!NOTE\]
>
> 补码(Two's Complement)与无符号(Unsigned)编码最本质的区别在于对位向量\*\*最高有效位(MSB)\*\*的权重解释。
>
> 对于一个 www 位的位向量 x⃗=\[xw−1,xw−2,...,x0\]\\vec{x} = \[x_{w-1}, x_{w-2}, \\dots, x_0\]x =\[xw−1,xw−2,...,x0\]:
>
> * **无符号编码 (B2UwB2U_wB2Uw)** :所有位均具有**正权重** 。
> B2Uw(x⃗)=∑i=0w−1xi⋅2i B2U_w(\\vec{x}) = \\sum_{i=0}\^{w-1} x_i \\cdot 2\^i B2Uw(x )=i=0∑w−1xi⋅2i
> *其最高位的权重为:+2w−1+2\^{w-1}+2w−1*
>
> * **补码编码 (B2TwB2T_wB2Tw)** :最高有效位具有**负权重** 。
> B2Tw(x⃗)=−xw−1⋅2w−1+∑i=0w−2xi⋅2i B2T_w(\\vec{x}) = \\mathbf{-x_{w-1} \\cdot 2\^{w-1}} + \\sum_{i=0}\^{w-2} x_i \\cdot 2\^i B2Tw(x )=−xw−1⋅2w−1+i=0∑w−2xi⋅2i
> *其最高位的权重为:−2w−1-2\^{w-1}−2w−1*
>
> 当位模式为 `1011` 时,两者的解释差异如下:
>
> | 编码方式 | 最高位权重 | 计算过程 | 最终数值 |
> |:--------|:-----------------------|:-------------------------------|:-------|
> | **无符号** | +23=8+2\^3 = 8+23=8 | 8+0+2+18 + 0 + 2 + 18+0+2+1 | **11** |
> | **补码** | −23=−8-2\^3 = -8−23=−8 | −8+0+2+1-8 + 0 + 2 + 1−8+0+2+1 | **-5** |
> \[!IMPORTANT\]
> > 练习题 2.17
> >
> > 假设 w=4w=4w=4,我们能给每个可能的十六进制数字赋予一个数值,假设用一个无符号或者补码表示。请根据这些表示,通过写出等式 (2.1) 和等式 (2.3) 所示的求和公式中的 2 的非零次幂,填写下表:
>
> 答案
>
> 根据无符号编码 B2UwB2U_wB2Uw(全部权重为正)和补码编码 B2TwB2T_wB2Tw(最高位权重为负)的定义,完成表格如下:
>
> | 十六进制 | 二进制 \\vec{x} | 无符号 B2U_4(\\vec{x}) | 补码 B2T_4(\\vec{x}) |
> |:--------|:-------------|:-------------------------------|:--------------------------------|
> | **0xE** | `[1110]` | 2\^3 + 2\^2 + 2\^1 = 14 | -2\^3 + 2\^2 + 2\^1 = -2 |
> | **0x0** | `[0000]` | 0 | 0 |
> | **0x5** | `[0101]` | 2\^2 + 2\^0 = 5 | 2\^2 + 2\^0 = 5 |
> | **0x8** | `[1000]` | 2\^3 = 8 | -2\^3 = -8 |
> | **0xD** | `[1101]` | 2\^3 + 2\^2 + 2\^0 = 13 | -2\^3 + 2\^2 + 2\^0 = -3 |
> | **0xF** | `[1111]` | 2\^3 + 2\^2 + 2\^1 + 2\^0 = 15 | -2\^3 + 2\^2 + 2\^1 + 2\^0 = -1 |
在计算机系统中,针对不同的字长 www,无符号数和补码表示的范围和特殊数值如下表所示:
| 数值符号 | 数值含义 | 字长 w=8w=8w=8 | 字长 w=16w=16w=16 | 字长 w=32w=32w=32 | 字长 w=64w=64w=64 |
|:---------------------|:---------|:-------------|:----------------|:--------------------------|:----------------------------------------------|
| **UMaxwUMax_wUMaxw** | 无符号最大值 | 0xFF 255 | 0xFFFF 65 535 | 0xFFFFFFFF 4 294 967 295 | 0xFFFFFFFFFFFFFFFF 18 446 744 073 709 551 615 |
| **TMinwTMin_wTMinw** | 补码最小值 | 0x80 -128 | 0x8000 -32 768 | 0x80000000 -2 147 483 648 | 0x8000000000000000 -9 223 372 036 854 775 808 |
| **TMaxwTMax_wTMaxw** | 补码最大值 | 0x7F 127 | 0x7FFF 32 767 | 0x7FFFFFFF 2 147 483 647 | 0x7FFFFFFFFFFFFFFF 9 223 372 036 854 775 807 |
| **-1** | 补码表示的 -1 | 0xFF | 0xFFFF | 0xFFFFFFFF | 0xFFFFFFFFFFFFFFFF |
| **0** | 数值 0 | 0x00 | 0x0000 | 0x00000000 | 0x0000000000000000 |
* **补码范围的不对称性** :补码能够表示的负数比正数多一个,即 ∣TMin∣=∣TMax∣+1\|TMin\| = \|TMax\| + 1∣TMin∣=∣TMax∣+1。这是因为一半的位模式(符号位为 1)表示负数,另一半位模式中,符号位为 0 的一部分表示非负数(包含 0),导致正数比负数少一个。
* **UMaxUMaxUMax 与 TMaxTMaxTMax 的关系** :最大无符号数值大约是补码最大值的两倍再加 1,公式为 UMaxw=2TMaxw+1UMax_w = 2TMax_w + 1UMaxw=2TMaxw+1。
* **特殊位模式** :
* **全 1 序列** :在补码表示中对应数值 −1-1−1,而在无符号表示中对应最大值 UMaxUMaxUMax。
* **全 0 序列** :在两种表示方式中都对应数值 000。
*** ** * ** ***
虽然 C 语言标准并未强制要求使用补码表示有符号整数,但几乎所有现代机器都采用补码。
* **`` 库** :定义了一组宏来限制不同整型数据的取值范围,如 `INT_MAX`、`INT_MIN` 和 `UINT_MAX`。
* **可移植性建议**:程序员应尽量编写假设典型的取值范围(如补码范围)的代码,以保证在大量机器和编译器上可移植。
与 C 语言不同,Java 对整数类型的取值范围和表示方法有非常严格且统一的要求:
* **强制补码表示**:Java 要求采用补码形式表示整数。
* **固定范围**:其取值范围在所有机器上表现完全一致。
* **单字节类型** :Java 中的单字节数据类型称为 `byte`(对应 8 位补码),而非 `char`。
> \[!NOTE\]
>
> 在 C 语言中,某些基本数据类型(如 `long`)在不同机器上的取值范围可能不同,这会影响程序的**可移植性** 。为了解决这一问题,ISO C99 标准在 `` 中引入了一类确定宽度的整数类型。
>
> * **类型声明** :形式为 `intN_t`(有符号)和 `uintN_t`(无符号),其中 NNN 的典型值为 8、16、32 和 64。
> * 例如:`uint16_t` 声明一个 16 位无符号变量;`int32_t` 声明一个 32 位有符号变量。
> * **范围宏定义** :这些类型对应一组宏,定义了每个 NNN 对应的最小值和最大值,如 `INTN_MIN`、`INTN_MAX` 和 `UINTN_MAX`。
>
> *** ** * ** ***
>
> 由于这些类型的具体实现与系统相关,打印它们时需要使用 `` 中定义的**宏**,以确保生成的格式字符串在不同编译环境下均正确。
>
> * **使用方式** :通过宏(如 `PRId32`、`PRIu64`)来扩展格式化字符串。
>
> * **示例代码**:
>
> ```c
> int32_t x;
> uint64_t y;
> // 使用宏进行跨平台格式化打印
> printf("x = %" PRId32 ", y = %" PRIu64 "\n", x, y);
> ```
>
> * **编译原理** :在 64 位程序中,宏 `PRId32` 通常展开为 `"d"`,而 `PRIu64` 展开为 `"lu"`。C 预处理器会将相邻的字符串常量拼接在一起,最终生成类似 `"x = %d, y = %lu\n"` 的字符串。
#### 反码
除了最高有效位的权重是 −(2w−1−1)-(2\^{w-1}-1)−(2w−1−1) 而不是 −2w−1-2\^{w-1}−2w−1,它和补码是一样的。
B2Ow(x⃗)≐−xw−1(2w−1−1)+∑i=0w−2xi2i B2O_w(\\vec{x}) \\doteq -x_{w-1}(2\^{w-1}-1) + \\sum_{i=0}\^{w-2} x_i 2\^i B2Ow(x )≐−xw−1(2w−1−1)+i=0∑w−2xi2i
对于位向量 \[1011\]\[1011\]\[1011\]:
* w=4w = 4w=4
* x3=1x_3 = 1x3=1
* x2=0,x1=1,x0=1x_2 = 0, x_1 = 1, x_0 = 1x2=0,x1=1,x0=1
B2O4(\[1011\])=−1⋅(24−1−1)+(0⋅22+1⋅21+1⋅20)=−1⋅(23−1)+(0+2+1)=−1⋅(8−1)+3=−7+3=−4 \\begin{aligned} B2O_4(\[1011\]) \&= -1 \\cdot (2\^{4-1} - 1) + (0 \\cdot 2\^2 + 1 \\cdot 2\^1 + 1 \\cdot 2\^0) \\\\ \&= -1 \\cdot (2\^3 - 1) + (0 + 2 + 1) \\\\ \&= -1 \\cdot (8 - 1) + 3 \\\\ \&= -7 + 3 \\\\ \&= -4 \\end{aligned} B2O4(\[1011\])=−1⋅(24−1−1)+(0⋅22+1⋅21+1⋅20)=−1⋅(23−1)+(0+2+1)=−1⋅(8−1)+3=−7+3=−4
#### 原码
最高有效位是符号位,用来确定剩下的位应该取负权重还是正权重。
B2Sw(x⃗)≐(−1)xw−1⋅(∑i=0w−2xi2i) B2S_w(\\vec{x}) \\doteq (-1)\^{x_{w-1}} \\cdot \\left( \\sum_{i=0}\^{w-2} x_i 2\^i \\right) B2Sw(x )≐(−1)xw−1⋅(i=0∑w−2xi2i)
对于位向量 \[1011\]\[1011\]\[1011\]:
* w=4w = 4w=4
* 最高位(符号位)x3=1x_3 = 1x3=1
* 剩余数值位 x2=0,x1=1,x0=1x_2 = 0, x_1 = 1, x_0 = 1x2=0,x1=1,x0=1
**计算步骤:**
1. **确定符号部分:**
(−1)x3=(−1)1=−1(-1)\^{x_3} = (-1)\^1 = -1(−1)x3=(−1)1=−1
2. **确定数值部分(绝对值):**
∑i=02xi2i=(0⋅22+1⋅21+1⋅20)=0+2+1=3\\sum_{i=0}\^{2} x_i 2\^i = (0 \\cdot 2\^2 + 1 \\cdot 2\^1 + 1 \\cdot 2\^0) = 0 + 2 + 1 = 3i=0∑2xi2i=(0⋅22+1⋅21+1⋅20)=0+2+1=3
3. **组合结果:**
B2S4(\[1011\])=−1⋅3=−3B2S_4(\[1011\]) = -1 \\cdot 3 = -3B2S4(\[1011\])=−1⋅3=−3
反码和原码对于数字 0 都有两种不同的编码方式:
* **+0 的表示:** 两种方法都将 \[00...0\]\[00\\dots0\]\[00...0\] 解释为 +0+0+0。
* **-0 的表示:** \* 在**原码** 中表示为 \[10...0\]\[10\\dots0\]\[10...0\]。
* 在**反码** 中表示为 \[11...1\]\[11\\dots1\]\[11...1\]。
#### 有符号与无符号的转换
C语言允许不同数字数据类型间强制类型转换,例如 `unsigned` 与 `int` 之间的转换。从位级角度看,转换**不改变位模式** ,**仅改变对位的解释方式**。
```c
// 强制类型转换仅改变解释方式,位表示不变。
// short 转 unsigned short
short int v = -12345;
unsigned short uv = (unsigned short) v;
printf("v = %d, uv = %u\n", v, uv);
// 输出 v = -12345, uv = 53191
// -12345 的 16 位补码表示与 53191 的 16 位无符号表示位模式完全相同。
```
```c
// 强制类型转换仅改变解释方式,位表示不变。
unsigned u = 4294967295u; /* UMax_32 */
int tu = (int) u;
printf("u = %u, tu = %d\n", u, tu);
// u = 4294967295, tu = -1
```
为了精确描述这种位模式不变的转换,我们定义以下函数:
UUU 无符号数、TTT补码表示、 BBB 位表示
* **U2Bw(x)U2B_w(x)U2Bw(x)** :将整数 xxx 映射为 www 位无符号数的位表示。
* **T2Bw(x)T2B_w(x)T2Bw(x)** :将整数 xxx 映射为 www 位补码形式的位表示。
* **B2Uw(u)B2U_w(u)B2Uw(u)** :将位向量 uuu 转换为无符号数。
* **B2Tw(u)B2T_w(u)B2Tw(u)** :将位向量 uuu 转换为补码形式的有符号数。
**补码转无符号数 (T2UT2UT2U)** :
T2Uw(x)≐B2Uw(T2Bw(x)) T2U_w(x) \\doteq B2U_w(T2B_w(x)) T2Uw(x)≐B2Uw(T2Bw(x))
输入范围:TMinw∼TMaxwTMin_w \\sim TMax_wTMinw∼TMaxw,结果范围:0∼UMaxw0 \\sim UMax_w0∼UMaxw。
**无符号数转补码 (U2TU2TU2T)** :
U2Tw(x)≐B2Tw(U2Bw(x)) U2T_w(x) \\doteq B2T_w(U2B_w(x)) U2Tw(x)≐B2Tw(U2Bw(x))
输入范围:0∼UMaxw0 \\sim UMax_w0∼UMaxw,结果范围:TMinw∼TMaxwTMin_w \\sim TMax_wTMinw∼TMaxw。
> \[!IMPORTANT\]
>
> 填写下列描述函数 T2U4T2U_4T2U4 的表格。该函数将 444 位补码数值 xxx 转换为对应的无符号数值。
>
> 通过保持底层位模式不变,将补码解释为无符号数。对于 4 位字长,2w=24=162\^w = 2\^4 = 162w=24=16。当 x\<0x \< 0x\<0 时,T2U4(x)=x+16T2U_4(x) = x + 16T2U4(x)=x+16。
>
> | 位模式 (十六进制) | 补码数值 xxx | 无符号数值 T2U4(x)T2U_4(x)T2U4(x) | 转换逻辑 |
> |:-----------|:---------|:-----------------------------|:---------------------------------------------|
> | `0x8` | −8-8−8 | **8** | −8+16=8-8 + 16 = 8−8+16=8 (TMinT_MinTMin) |
> | `0xD` | −3-3−3 | **13** | −3+16=13-3 + 16 = 13−3+16=13 |
> | `0xE` | −2-2−2 | **14** | −2+16=14-2 + 16 = 14−2+16=14 |
> | `0xF` | −1-1−1 | **15** | −1+16=15-1 + 16 = 15−1+16=15 (UMaxU_MaxUMax) |
> | `0x0` | 000 | **0** | 数值保持不变 |
> | `0x5` | 555 | **5** | 数值保持不变 |
>
> * 对于非负数 (x≥0x \\ge 0x≥0),数值保持不变。
> * 对于负数 (x\<0x \< 0x\<0),无符号值等于补码值加上 2w2\^w2w。
*** ** * ** ***
**补码转无符号数**
对于满足 TMinw≤x≤TMaxwTMin_w \\le x \\le TMax_wTMinw≤x≤TMaxw 的补码数值 xxx,其转换为无符号数的公式为:
T2Uw(x)={x+2w,x\<0x,x≥0(2.5) T2U_w(x) = \\begin{cases} x + 2\^w, \& x \< 0 \\\\ x, \& x \\ge 0 \\end{cases} \\qquad (2.5) T2Uw(x)={x+2w,x,x\<0x≥0(2.5)
* **负数转换** :T2U16(−12345)=−12345+216=53191T2U_{16}(-12345) = -12345 + 2\^{16} = 53191T2U16(−12345)=−12345+216=53191
* **边界情况** :T2Uw(−1)=−1+2w=UMaxwT2U_w(-1) = -1 + 2\^w = UMax_wT2Uw(−1)=−1+2w=UMaxw
通过比较无符号数解码公式 B2UB2UB2U 和补码解码公式 B2TB2TB2T,可以发现:
对于位模式 x⃗\\vec{x}x ,**从 000 到 w−2w-2w−2 位的加权和在两种表示中是完全相同的,它们会在减法中互相抵消**。
唯一不同的位是**最高有效位(MSB)**:
* **无符号差值计算** :B2Uw(x⃗)−B2Tw(x⃗)=xw−1(2w−1−(−2w−1))=xw−12wB2U_w(\\vec{x}) - B2T_w(\\vec{x}) = x_{w-1}(2\^{w-1} - (-2\^{w-1})) = x_{w-1} 2\^wB2Uw(x )−B2Tw(x )=xw−1(2w−1−(−2w−1))=xw−12w
由此得到转换关系式:
B2Uw(T2Bw(x))=T2Uw(x)=x+xw−12w(2.6) B2U_w(T2B_w(x)) = T2U_w(x) = x + x_{w-1} 2\^w \\qquad (2.6) B2Uw(T2Bw(x))=T2Uw(x)=x+xw−12w(2.6)
在补码表示中,位 xw−1x_{w-1}xw−1 决定了 xxx 的符号:
* 如果 x\<0x \< 0x\<0(负数),则 xw−1=1x_{w-1} = 1xw−1=1,值增加 2w2\^w2w。
* 如果 x≥0x \\ge 0x≥0(非负数),则 xw−1=0x_{w-1} = 0xw−1=0,值保持不变。
*** ** * ** ***
**无符号数转补码**
对于满足 0≤u≤UMaxw0 \\le u \\le UMax_w0≤u≤UMaxw 的无符号数 uuu,其转换为补码 U2Tw(u)U2T_w(u)U2Tw(u) 的关系如下:
U2Tw(u)={u,u≤TMaxwu−2w,u\>TMaxw(2.7) U2T_w(u) = \\begin{cases} u, \& u \\le TMax_w \\\\ u - 2\^w, \& u \> TMax_w \\end{cases} \\qquad (2.7) U2Tw(u)={u,u−2w,u≤TMaxwu\>TMaxw(2.7)
设位向量为 u⃗\\vec{u}u ,其补码表示为 U2Tw(u)U2T_w(u)U2Tw(u)。结合无符号和补码的定义公式,可以得出:
U2Tw(u)=−uw−12w−1+u(2.8) U2T_w(u) = -u_{w-1} 2\^{w-1} + u \\qquad (2.8) U2Tw(u)=−uw−12w−1+u(2.8)
在 uuu 的无符号表示中,最高位 uw−1u_{w-1}uw−1 决定了 uuu 是否大于 TMaxw=2w−1−1TMax_w = 2\^{w-1} - 1TMaxw=2w−1−1:
* 当 uw−1=0u_{w-1} = 0uw−1=0 时,uuu 在补码表示的范围内。
* 当 uw−1=1u_{w-1} = 1uw−1=1 时,uuu 超出了补码的正数范围,转换后变为负数。
*** ** * ** ***
无符号数与补码之间的相互转换,在位级(Bit-level)表示上是不变的,只是解释位的方式发生了变化。
| 转换方向 | 范围条件 | 结果 |
|:-----------------|:---------------------------------------|:-----------------|
| **通用** | 0≤x≤TMaxw0 \\le x \\le TMax_w0≤x≤TMaxw | 保持不变 |
| **T2U (补码转无符号)** | x\<0x \< 0x\<0 | x+2wx + 2\^wx+2w |
| **U2T (无符号转补码)** | u\>TMaxwu \> TMax_wu\>TMaxw | u−2wu - 2\^wu−2w |
#### C中的有符号数与无符号数
**默认是有符号**
C 语言支持所有整型数据类型的**有符号** 和**无符号** 运算。尽管 C 语言标准没有指定有符号数要采用某一种表示,但是几乎所有的机器都使用**补码**。
通常,**大多数数字都默认为是有符号的** 。例如,声明 `12345` 或 `0x1A2B` 时,它们被视为有符号数。
要创建无符号常量,必须加上后缀字符 `U` 或 `u`(如 `12345U` 或 `0x1A2Bu`)。
**类型转换不改变底层定义**
C 语言允许无符号数和有符号数之间的转换。大多数系统遵循的原则是:**底层的位表示保持不变**。
* **T2UwT2U_wT2Uw**:从补码(有符号)转换为无符号数。
* **U2TwU2T_wU2Tw**:从无符号数转换为补码(有符号)。
* 其中 www 表示数据类型的位数。
**显式与隐式转换**
**显式强制类型转换**:通过代码手动指定目标类型。
```c
int tx, ty;
unsigned ux, uy;
tx = (int) ux;
uy = (unsigned) ty;
```
**隐式类型转换**:当一种类型的表达式被赋值给另外一种类型的变量时,转换会自动发生。
```c
int tx, ty;
unsigned ux, uy;
tx = ux; /* Cast to signed */
uy = ty; /* Cast to unsigned */
```
**printf 输出处理**
`printf` 函数不使用任何类型信息,它仅根据格式指示符来解释内存中的位模式:
* `%d`:有符号十进制
* `%u`:无符号十进制
* `%x`:十六进制
```c
int x = -1;
unsigned u = 2147483648; /* 2 to the 31st */
printf("x = %u = %d\n", x, x);
printf("u = %u = %d\n", u, u);
/* 32 位机器输出结果:
x = 4294967295 = -1
u = 2147483648 = -2147483648
*/
```
**表达式中的隐式转换规则**
当一个运算中同时包含有符号和无符号数时,**C 语言会隐式地将有符号参数转换为无符号数** ,并假设这两个数都是非负的。这在执行关系运算(如 `<`、`>`)时经常会导致非直观的结果。
| 表达式 | 类型 | 求值 |
|:---------------------------------|:----|:----|
| `0 == 0U` | 无符号 | 1 |
| `-1 < 0` | 有符号 | 1 |
| `-1 < 0U` | 无符号 | 0\* |
| `2147483647 > -2147483647-1` | 有符号 | 1 |
| `2147483647U > -2147483647-1` | 无符号 | 0\* |
| `2147483647 > (int) 2147483648U` | 有符号 | 1\* |
| `-1 > -2` | 有符号 | 1 |
| `(unsigned) -1 > -2` | 无符号 | 1 |
*注:标有 `*` 的行表示其结果可能由于隐式转换而违背直觉。例如 `-1 < 0U`,由于 `-1` 转换为无符号数是巨大的正数,因此结果为假(0)。*
> \[!IMPORTANT\]
> > 假设在采用 **补码(Two's Complement)** 运算的 **32 位** 机器上对以下表达式进行求值。
> >
> > 根据 C 语言的 **升级规则(Promotion Rule)**:当一个运算数是无符号的,另一个运算数也会被强制转换为无符号数再进行比较。
>
> 请填写下表,描述表达式的类型以及求值结果(1 表示真,0 表示假)。
>
> | 表达式 | 类型 | 求值 |
> |:---------------------------------|:---|:---|
> | `-2147483647 - 1 == 2147483648U` | | |
> | `-2147483647 - 1 < 2147483647` | | |
> | `-2147483647 - 1U < 2147483647` | | |
> | `-2147483647 - 1 < -2147483647` | | |
> | `-2147483647 - 1U < -2147483647` | | |
>
> 在 32 位补码机器中:
>
> * **TMin (有符号最小负数)** : `-2147483647 - 1` = `-2147483648`,其位模式为 `0x80000000`。
> * **TMax (有符号最大正数)** : `2147483647`,其位模式为 `0x7FFFFFFF`。
>
> 1. `-2147483647 - 1 == 2147483648U`
>
> * **计算** :左侧是 `int`,右侧是 `unsigned`。
> * **转换** :左侧结果 `-2147483648` 被强制转换为无符号数。
> * **逻辑** :`-2147483648` 的位模式 `0x80000000` 在无符号解释下等于 2312\^{31}231,即 `2147483648`。
> * **结果** :类型为 **无符号数** ,求值为 **1**。
>
> 2. `-2147483647 - 1 < 2147483647`
>
> * **计算** :两边都是 `int`。
> * **逻辑** :普通的有符号数比较,`-2147483648 < 2147483647` 成立。
> * **结果** :类型为 **有符号数** ,求值为 **1**。
>
> 3. `-2147483647 - 1U < 2147483647`
>
> * **计算** :左侧包含 `1U`,导致整个表达式变为无符号比较。
> * **转换** :右侧的 `2147483647` 被转换为无符号数(值不变)。左侧计算出的位模式 `0x80000000` 转换为无符号数 `2147483648`。
> * **逻辑** :比较 `2147483648 < 2147483647`,不成立。
> * **结果** :类型为 **无符号数** ,求值为 **0**。
>
> 4. `-2147483647 - 1 < -2147483647`
>
> * **计算** :两边都是 `int`。
> * **逻辑** :有符号数比较,`-2147483648 < -2147483647` 成立。
> * **结果** :类型为 **有符号数** ,求值为 **1**。
>
> 5. `-2147483647 - 1U < -2147483647`
>
> * **计算** :左侧包含 `1U`,全式提升为无符号比较。
> * **转换** :
> * 左侧:`-2147483648` 的位模式 `0x80000000` →\\rightarrow→ 无符号 `2147483648`。
> * 右侧:`-2147483647` 的位模式 `0x80000001` →\\rightarrow→ 无符号 `2147483649`。
> * **逻辑** :`2147483648 < 2147483649` 成立。
> * **结果** :类型为 **无符号数** ,求值为 **1**。
>
> | 表达式 | 类型 | 求值 |
> |:---------------------------------|:---------|:------|
> | `-2147483647 - 1 == 2147483648U` | **无符号数** | **1** |
> | `-2147483647 - 1 < 2147483647` | **有符号数** | **1** |
> | `-2147483647 - 1U < 2147483647` | **无符号数** | **0** |
> | `-2147483647 - 1 < -2147483647` | **有符号数** | **1** |
> | `-2147483647 - 1U < -2147483647` | **无符号数** | **1** |
#### 拓展数字
在一个不同字长的整数之间进行转换,同时保持数值不变,是计算机底层常见的运算。
* **从小向大转换**:从较小的数据类型转换到较大的类型,总是可能的。
* **从大向小转换**:可能发生溢出或截断,导致数值改变。
**无符号数的零扩展**
将一个无符号数转换为更大的数据类型时,只需在开头添加 0。
**原理** :定义宽度为 www 的位向量 u⃗=\[ww−1,ww−2,...,w0\]\\vec{u} = \[w_{w-1}, w_{w-2}, \\dots, w_0\]u =\[ww−1,ww−2,...,w0\] 和宽度为 w′w'w′ 的位向量 u⃗′\\vec{u}'u ′,其中 w′\>ww' \> ww′\>w。则 u⃗′=\[0,...,0,ww−1,ww−2,...,w0\]\\vec{u}' = \[0, \\dots, 0, w_{w-1}, w_{w-2}, \\dots, w_0\]u ′=\[0,...,0,ww−1,ww−2,...,w0\]。
**依据**:该原理直接遵循无符号数编码的定义,高位的权值为 0,不影响数值。
**补码数字的符号扩展**
将一个补码数字转换为更大的数据类型时,在表示中添加\*\*最高有效位(符号位)\*\*的值。
**原理** :定义宽度为 www 的位向量 x⃗=\[xw−1,xw−2,...,x0\]\\vec{x} = \[x_{w-1}, x_{w-2}, \\dots, x_0\]x =\[xw−1,xw−2,...,x0\] 和宽度为 w′w'w′ 的位向量 x⃗′\\vec{x}'x ′,其中 w′\>ww' \> ww′\>w。则 x⃗′=\[xw−1,...,xw−1,xw−1,xw−2,...,x0\]\\vec{x}' = \[x_{w-1}, \\dots, x_{w-1}, x_{w-1}, x_{w-2}, \\dots, x_0\]x ′=\[xw−1,...,xw−1,xw−1,xw−2,...,x0\]。
> \[!NOTE\]
>
> * `sx = -12345`:16 位十六进制为 `cf c7`。
> * `x = (int)sx`:扩展到 32 位。符号位为 1(`c` 的二进制是 `1100`),填充 `f`,得到 `ff ff cf c7`。
> * `usx = 53191`:16 位无符号表示同样为 `cf c7`。
> * `ux = (unsigned)usx`:执行零扩展,填充 0,得到 `00 00 cf c7`。
**符号扩展的证明**
通过归纳法证明:**扩展 1 位且保持数值不变,即可证明扩展任意位。**
**证明目标** :
B2Tw+1(\[xw−1,xw−1,xw−2,...,x0\])=B2Tw(\[xw−1,xw−2,...,x0\]) B2T_{w+1}(\[x_{w-1}, x_{w-1}, x_{w-2}, \\dots, x_0\]) = B2T_w(\[x_{w-1}, x_{w-2}, \\dots, x_0\]) B2Tw+1(\[xw−1,xw−1,xw−2,...,x0\])=B2Tw(\[xw−1,xw−2,...,x0\])
**推导过程** :
B2Tw+1(\[xw−1,xw−1,xw−2,...,x0\])=−xw−12w+∑i=0w−1xi2i=−xw−12w+xw−12w−1+∑i=0w−2xi2i其中(−2w+2w−1)xw−1=−2w−1xw−1=−xw−12w−1+∑i=0w−2xi2i=B2Tw(x⃗) B2T_{w+1}(\[x_{w-1}, x_{w-1}, x_{w-2}, \\dots, x_0\]) =-x_{w-1} 2\^w + \\sum_{i=0}\^{w-1} x_i 2\^i \\\\ = -x_{w-1} 2\^w + x_{w-1} 2\^{w-1} + \\sum_{i=0}\^{w-2} x_i 2\^i \\\\ 其中 (-2\^w + 2\^{w-1})x_{w-1} = -2\^{w-1}x_{w-1} \\\\ = -x_{w-1} 2\^{w-1} + \\sum_{i=0}\^{w-2} x_i 2\^i = B2T_w(\\vec{x}) B2Tw+1(\[xw−1,xw−1,xw−2,...,x0\])=−xw−12w+i=0∑w−1xi2i=−xw−12w+xw−12w−1+i=0∑w−2xi2i其中(−2w+2w−1)xw−1=−2w−1xw−1=−xw−12w−1+i=0∑w−2xi2i=B2Tw(x )
新增位的权值为 −2w-2\^w−2w,而原符号位权值从 −2w−1-2\^{w-1}−2w−1 变为 +2w−1+2\^{w-1}+2w−1。两者的综合效果是 −2w+2w−1=−2w−1-2\^w + 2\^{w-1} = -2\^{w-1}−2w+2w−1=−2w−1,保持了原始数值的偏移量。
```c
#include
// 32位大端法机器运行
typedef unsigned char *byte_pointer;
void show_bytes(byte_pointer start, size_t len) {
size_t i;
for (i = 0; i < len; i++)
printf(" %.2x", start[i]);
printf("\n");
}
int main() {
short sx = -12345; /* -12345 */
unsigned short usx = sx; /* 53191 */
int x = sx; /* -12345 */
unsigned ux = usx; /* 53191 */
printf("sx = %d:\t", sx);
show_bytes((byte_pointer) &sx, sizeof(short));
// sx = -12345: c7 cf
printf("usx = %u:\t", usx);
show_bytes((byte_pointer) &usx, sizeof(unsigned short));
// usx = 53191: c7 cf
printf("x = %d:\t", x);
show_bytes((byte_pointer) &x, sizeof(int));
// x = -12345: c7 cf ff ff
printf("ux = %u:\t", ux);
show_bytes((byte_pointer) &ux, sizeof(unsigned));
// ux = 53191: c7 cf 00 00
return 0;
}
```
**相对顺序的影响**
在 C 语言中,当一个程序同时需要改变数据的大小(如 `short` 到 `int`)以及转换有无符号属性(如 `signed` 到 `unsigned`)时,转换的**相对顺序**会直接影响最终的数值结果。
根据 C 语言标准的要求,当执行此类复合转换时,系统遵循以下顺序:
* **第一步:改变大小**(进行符号扩展或零扩展)。
* **第二步:符号转换**(从有符号转换为无符号,或反之)。
**先进行拓展,再进行转换**
考虑以下代码段,其中 `sx` 是一个 16 位有符号短整型,其值为 `-12345`:
```c
#include
typedef unsigned char *byte_pointer;
void show_bytes(byte_pointer start, size_t len) {
size_t i;
for (i = 0; i < len; i++)
printf(" %.2x", start[i]);
printf("\n");
}
int main() {
short sx = -12345; /* -12345 */
unsigned uy = sx; /* Mystery! */
printf("uy = %u:\t", uy);
show_bytes((byte_pointer) &uy, sizeof(unsigned));
// 输出 uy = 4294954951: c7 cf ff ff
return 0;
}
```
在大端法机器上,执行上述代码会产生如下输出:
`uy = 4294954951: ff ff cf c7`
**转换过程详解:**
1. **原始值** :`sx = -12345`。其 16 位补码表示为 `cf c7`。
2. **符号扩展** :由于 `sx` 是有符号数,转换到 32 位时进行**符号扩展** 。高 16 位补符号位(1),得到 `ff ff cf c7`。此时数值在逻辑上仍为 `-12345`(如果是 32 位有符号 `int`)。
3. **解释为无符号** :将 `ff ff cf c7` 直接解释为 `unsigned` 类型,得到的十进制数值即为 `4,294,954,951`。
| 转换路径 | 等价表达式 | 内存字节 (大端法) | 十进制结果 |
|:-----------|:--------------------------------|:--------------|:------------------|
| **标准要求路径** | `(unsigned)(int) sx` | `ff ff cf c7` | **4,294,954,951** |
| **错误路径** | `(unsigned)(unsigned short) sx` | `00 00 cf c7` | 53,191 |
**结论** :事实证明,C 语言会先将 `short` 符号扩展为 `int`,然后再将其转换为 `unsigned`。
> \[!IMPORTANT\]
> > 考虑下面的 C 函数:
> >
> > ```c
> > int fun1(unsigned word) {
> > return (int) ((word << 24) >> 24);
> > }
> >
> > int fun2(unsigned word) {
> > return ((int) word << 24) >> 24;
> > }
> > ```
> >
> > 假设在一个采用补码运算的机器上以 32 位程序来执行这些函数。还假设有符号数值的右移是**算术右移** ,而无符号数值的右移是**逻辑右移**。
>
> * **`fun1`** :提取参数的最低字节(Byte 0),并将其余 24 位**清零** 。其结果始终是一个正整数(000 到 255255255)。
> * **`fun2`** :提取参数的最低字节,并进行**符号扩展**。如果该字节的最高位(第 7 位)是 1,则结果的高 24 位全补 1,将其解释为负数。
>
> | `w` | `fun1(w)` | `fun2(w)` |
> |:-------------|:-------------|:-------------|
> | `0x00000076` | `0x00000076` | `0x00000076` |
> | `0x87654321` | `0x00000021` | `0x00000021` |
> | `0x000000C9` | `0x000000C9` | `0xFFFFFFC9` |
> | `0xEDCBA987` | `0x00000087` | `0xFFFFFF87` |
#### 截断数字
截断数字是指减少表示一个数字的位数。例如,当我们将一个 `int`(32位)强制类型转换为 `short`(16位)时,高位会被丢弃,这可能会改变数值的大小,这是**溢出**的一种形式。
*** ** * ** ***
**截断无符号数**
假设将一个 www 位的数 x⃗=\[xw−1,xw−2,...,x0\]\\vec{x} = \[x_{w-1}, x_{w-2}, \\dots, x_0\]x =\[xw−1,xw−2,...,x0\] 截断为一个 kkk 位数字时,我们会丢弃高 w−kw-kw−k 位,得到一个位向量 x⃗′=\[xk−1,xk−2,...,x0\]\\vec{x}' = \[x_{k-1}, x_{k-2}, \\dots, x_0\]x ′=\[xk−1,xk−2,...,x0\]。
令 x=B2Uw(x⃗)x = B2U_w(\\vec{x})x=B2Uw(x ),则截断后的值 x′=x mod 2kx' = x \\bmod 2\^kx′=xmod2k。
该原理背后的直觉是所有被截断的位其权重形式都为 2i2\^i2i,其中 i≥ki \\geq ki≥k,因此,每一个权重在取模操作下结果都为零。
B2Uw(\[xw−1,xw−2,...,x0\]) mod 2k=\[∑i=0w−1xi2i\] mod 2k=\[∑i=0k−1xi2i\] mod 2k=∑i=0k−1xi2i=B2Uk(\[xk−1,...,x0\]) B2U_w(\[x_{w-1}, x_{w-2}, \\dots, x_0\]) \\bmod 2\^k = \\left\[ \\sum_{i=0}\^{w-1} x_i 2\^i \\right\] \\bmod 2\^k \\\\= \\left\[ \\sum_{i=0}\^{k-1} x_i 2\^i \\right\] \\bmod 2\^k \\\\= \\sum_{i=0}\^{k-1} x_i 2\^i \\\\ = B2U_k(\[x_{k-1}, \\dots, x_0\]) B2Uw(\[xw−1,xw−2,...,x0\])mod2k=\[i=0∑w−1xi2i\]mod2k=\[i=0∑k−1xi2i\]mod2k=i=0∑k−1xi2i=B2Uk(\[xk−1,...,x0\])
在此推导中,利用了属性:对于任何 i≥ki \\geq ki≥k,2i mod 2k=02\^i \\bmod 2\^k = 02imod2k=0。
*** ** * ** ***
**截断补码数值**
补码截断也具有相似的属性,只不过要将最高位转换为符号位。令 x=B2Uw(x⃗)x = B2U_w(\\vec{x})x=B2Uw(x ),x′=B2Tk(x⃗′)x' = B2T_k(\\vec{x}')x′=B2Tk(x ′)。则 x′=U2Tk(x mod 2k)x' = U2T_k(x \\bmod 2\^k)x′=U2Tk(xmod2k)。
* 在这个公式中,x mod 2kx \\bmod 2\^kxmod2k 将是 000 到 2k−12\^k-12k−1 之间的一个数。
* 应用函数 U2TkU2T_kU2Tk 产生的效果是把最高有效位 xk−1x_{k-1}xk−1 的权重从 2k−12\^{k-1}2k−1 转变为 −2k−1-2\^{k-1}−2k−1。
示例将数值 x=53191x = 53191x=53191 从 `int` 转换为 `short`:
* 由于 216=65536≥x2\^{16} = 65536 \\geq x216=65536≥x,我们有 x mod 216=x=53191x \\bmod 2\^{16} = x = 53191xmod216=x=53191。
* 当把这个数转换为 16 位的补码时,得到 x′=53191−65536=−12345x' = 53191 - 65536 = -12345x′=53191−65536=−12345。
综上有:
* **无符号数的截断结果:**
B2Uk(\[xk−1,xk−2,...,x0\])=B2Uw(\[xw−1,xw−2,...,x0\]) mod 2k(2.9)B2U_k(\[x_{k-1}, x_{k-2}, \\dots, x_0\]) = B2U_w(\[x_{w-1}, x_{w-2}, \\dots, x_0\]) \\bmod 2\^k \\quad (2.9)B2Uk(\[xk−1,xk−2,...,x0\])=B2Uw(\[xw−1,xw−2,...,x0\])mod2k(2.9)
* **补码数字的截断结果:**
B2Tk(\[xk−1,xk−2,...,x0\])=U2Tk(B2Uw(\[xw−1,xw−2,...,x0\]) mod 2k)(2.10)B2T_k(\[x_{k-1}, x_{k-2}, \\dots, x_0\]) = U2T_k(B2U_w(\[x_{w-1}, x_{w-2}, \\dots, x_0\]) \\bmod 2\^k) \\quad (2.10)B2Tk(\[xk−1,xk−2,...,x0\])=U2Tk(B2Uw(\[xw−1,xw−2,...,x0\])mod2k)(2.10)
```c
#include
typedef unsigned char *byte_pointer;
void show_bytes(byte_pointer start, size_t len) {
size_t i;
for (i = 0; i < len; i++)
printf(" %.2x", start[i]);
printf("\n");
}
int main() {
// 1. 初始值:53191
// 二进制: 1100 1111 1100 0111 (十六进制: 0x0000CFC7)
int x = 53191;
// 2. 强制转换为 short (16位)
// 发生截断:只保留低 16 位。
// 0xCFC7 在 16 位有符号数中,最高位是 1,代表负数。
// 根据补码计算:0xCFC7 = -32768 + 20423 = -12345
short sx = (short) x;
// 3. 将 short 扩展回 int (32位)
// 发生符号扩展:为了保持数值 -12345 不变,高位全部填充符号位 (1)。
// 结果变为: 0xFFFFCFC7
int y = sx;
printf("x = %d, bytes: ", x);
show_bytes((byte_pointer) &x, sizeof(int));
// x = 53191, bytes: c7 cf 00 00
printf("sx = %d, bytes: ", sx);
show_bytes((byte_pointer) &sx, sizeof(short));
// sx = -12345, bytes: c7 cf
printf("y = %d, bytes: ", y);
show_bytes((byte_pointer) &y, sizeof(int));
// y = -12345, bytes: c7 cf ff ff
return 0;
}
```
#### 例题
> **题目**
>
> **练习题 2.25** 考虑下列代码,这段代码试图计算数组 a 中所有元素的和,其中元素的数量由参数 length 给出。
>
> ```c
> /* WARNING: This is buggy code */
> float sum_elements(float a[], unsigned length) {
> int i;
> float result = 0;
> for (i = 0; i <= length-1; i++)
> result += a[i];
> return result;
> }
> ```
>
> 当参数 length 等于 0 时,运行这段代码应该返回 0.0。但实际上,运行时会遇到一个内存错误。请解释为什么会发生这样的情况,并且说明如何修改代码。
**答案**
* **原因分析** :因为参数 `length` 是无符号的,计算 0−10-10−1 将使用无符号运算,这等价于模数加法。结果得到 UMaxUMaxUMax(无符号整数的最大值)。≤\\le≤ 比较同样使用无符号数比较,因为任何数都是小于或者等于 UMaxUMaxUMax 的,所以这个比较总是为真!因此,代码将试图访问数组 `a` 的非法元素。
* **解决方法** :有两种方法可以改正这段代码:
1. 将 `length` 声明为 `int` 类型。
2. 将 `for` 循环的测试条件改为 `i < length`。
> **题目**
>
> **练习题 2.26** 现在给你一个任务,写一个函数用来判定一个字符串是否比另一个更长。前提是你要用字符串库函数 `strlen`,它的声明如下:
>
> ```c
> /* Prototype for library function strlen */
> size_t strlen(const char *s);
> ```
>
> 最开始你写的函数是这样的:
>
> ```c
> /* Determine whether string s is longer than string t */
> /* WARNING: This function is buggy */
> int strlonger(char *s, char *t) {
> return strlen(s) - strlen(t) > 0;
> }
> ```
>
> 当你以一些示例数据上测试这个函数时,一切似乎都是正确的。进一步研究发现,在头文件 `stdio.h` 中数据类型 `size_t` 是定义成 `unsigned int` 的。
>
> A. 在什么情况下,这个函数会产生不正确的结果?
>
> B. 解释为什么会出现这样不正确的结果。
>
> C. 说明如何修改这段代码好让它能可靠地工作。
**答案**
**2.26** 这个例子说明了无符号运算的一个细微的特性,同时也是我们执行无符号运算时不会意识到的属性。这会导致一些非常棘手的错误。
* **A. 错误情况** :当 `s` 比 `t` 短的时候,该函数会不正确地返回 1。
* **B. 原因解释** :由于 `strlen` 被定义为产生一个无符号的结果,差和比较都采用无符号运算来计算。当 `s` 比 `t` 短的时候,`strlen(s) - strlen(t)` 的差会为负,但是变成了一个很大的无符号数,且大于 0。
* **C. 修改方法** :将测试语句改成 `return strlen(s) > strlen(t);`。因为这样是直接对两个数值进行比较,没有产生中间运算结果。