6 整数
在计算机底层,所有的数据都是以 0 和 1 的二进制位(bit)形式存储的。计算机硬件本身并不知道这些 0 和 1 代表什么意义,因此我们需要建立一套**"解释规则"**,将二进制串映射为人类能够理解的整数。
1. 数据表示基础:无符号数与补码
为了满足不同的计算需求,计算机科学家设计了两种最基础的整数表示方式:
-
无符号数(Unsigned)
- 定义:只能表示 0 和正整数,没有负数。
- 特点:所有的二进制位都代表正的权重。
- 应用场景 :用于表示绝对不可能为负数的物理量或逻辑量,例如内存地址(指针)、文件大小、数组长度等。在 C 语言中对应
unsigned int等类型。
-
补码(Two's Complement)
- 定义 :现代计算机默认的有符号数编码方式,可以表示正数、负数和 0。
- 特点 :最高位(最左边的一位)被征用为符号位 ,但它不仅仅代表正负,而是代表一个巨大的负数权重。
- 应用场景 :用于日常的常规算术运算。在 C 语言中对应
int,short,long等默认类型。
2. 核心映射规则:B2U 与 B2T
B2U 和 B2T 是两个核心的数学函数,它们定义了如何将一个长度为 www 位的二进制序列 x⃗=[xw−1,xw−2,...,x0]\vec{x} = [x_{w-1}, x_{w-2}, \dots, x_0]x =[xw−1,xw−2,...,x0] 翻译成十进制整数。
2.1 B2U (Binary to Unsigned)
- 原理 :每一位 xix_ixi 都代表一个正的权重 2i2^i2i。直接将所有位按权重相加。
- 数学公式 :
B2Uw(x⃗)=∑i=0w−1xi⋅2iB2U_w(\vec{x}) = \sum_{i=0}^{w-1} x_i \cdot 2^iB2Uw(x )=∑i=0w−1xi⋅2i
2.2 B2T (Binary to Two's Complement)
- 原理 :最高位 xw−1x_{w-1}xw−1 的权重是负的 (−2w−1-2^{w-1}−2w−1),而其余所有位的权重依然是正的(2i2^i2i)。
- 数学公式 :
B2Tw(x⃗)=−xw−1⋅2w−1+∑i=0w−2xi⋅2iB2T_w(\vec{x}) = -x_{w-1} \cdot 2^{w-1} + \sum_{i=0}^{w-2} x_i \cdot 2^iB2Tw(x )=−xw−1⋅2w−1+∑i=0w−2xi⋅2i
💡 实例解析(以 4 位二进制
1011为例,即 w=4w=4w=4)
- 用 B2U 规则解释 :1⋅23+0⋅22+1⋅21+1⋅20=8+0+2+1=111 \cdot 2^3 + 0 \cdot 2^2 + 1 \cdot 2^1 + 1 \cdot 2^0 = 8 + 0 + 2 + 1 = 111⋅23+0⋅22+1⋅21+1⋅20=8+0+2+1=11
- 用 B2T 规则解释 :−1⋅23+0⋅22+1⋅21+1⋅20=−8+0+2+1=−5-1 \cdot 2^3 + 0 \cdot 2^2 + 1 \cdot 2^1 + 1 \cdot 2^0 = -8 + 0 + 2 + 1 = -5−1⋅23+0⋅22+1⋅21+1⋅20=−8+0+2+1=−5
结论 :同样的底层二进制数据
1011,使用不同的解释规则,得到的值完全不同。
3. C 语言中的强制类型转换
在 C 语言中,我们经常会在有符号数(int)和无符号数(unsigned int)之间进行强制类型转换。
- 核心本质 :底层二进制位(0 和 1)绝对不改变! 仅仅是改变了 CPU "看待"这些数据的方式。
- 形象比喻 :相当于给 CPU 换了一副"眼镜"。内存里存的始终是
1011,戴上"补码眼镜"看到的是-5,摘下换上"无符号眼镜"看到的就变成了11。
c
int a = -5; // 内存中存的二进制是 1011 (补码)
unsigned int b = (unsigned int)a; // 强制转换!内存依然是 1011,但 b 的值变成了 11
4. 转换的数值变化规律(T2U 与 U2T)
在有符号数(T)和无符号数(U)之间进行转换(即"正反向走")时,由于底层的位模式不变,数值的变化遵循严格的数学规律。假设数据总位数为 www:
4.1 规律一:最高位是 0(原本是正数或 0)
- 变化结果 :数值完全不变。
- 原因 :最高位是 0 时,无论是 B2U 还是 B2T,最高位的权重(0⋅2w−10 \cdot 2^{w-1}0⋅2w−1 或 0⋅−2w−10 \cdot -2^{w-1}0⋅−2w−1)都是 0,其余位的计算方式完全一致。
4.2 规律二:最高位是 1(原本是负数,或无符号的大数)
- 变化结果 :数值发生突变,且差值永远是 2w2^w2w。
- 原因 :最高位在无符号中是 +2w−1+2^{w-1}+2w−1,在补码中是 −2w−1-2^{w-1}−2w−1。两者的差值为 (+2w−1)−(−2w−1)=2w(+2^{w-1}) - (-2^{w-1}) = 2^w(+2w−1)−(−2w−1)=2w。
具体转换方向:
-
方向 A:有符号 转 无符号 (T2U)
- 现象:负数会变成一个极大的正数。
- 公式 :新值=原值+2w新值 = 原值 + 2^w新值=原值+2w
- 示例 (w=4w=4w=4):
-5转无符号 →−5+24=−5+16=11\rightarrow -5 + 2^4 = -5 + 16 = 11→−5+24=−5+16=11
-
方向 B:无符号 转 有符号 (U2T)
- 现象:极大的正数会突然变成负数。
- 公式 :新值=原值−2w新值 = 原值 - 2^w新值=原值−2w
- 示例 (w=4w=4w=4):
11转有符号 →11−24=11−16=−5\rightarrow 11 - 2^4 = 11 - 16 = -5→11−24=11−16=−5
避坑指南
在 C 语言编程中,尽量避免将有符号数和无符号数放在一起进行比较或运算 。
例如:0U - 1。由于 0U 是无符号数,-1 会被隐式转换为无符号数,其底层全为 1 的位模式会被解释为一个极其巨大的正数(在 32 位系统中为 4294967295),这往往是导致死循环或越界 Bug 的万恶之源。
C 语言中,对于 8 位有符号整数 b、c,c=-128,b=-c,则 b=-128
因为 c=10000000,对 c 取补(按位取反,+1),得出 b=10000000=-128