1. 引言
在计算机系统中,数据以二进制形式存储。编程语言如 C、Java 或 Python 中,变量可以以 8 位、16 位、32 位或 64 位的形式存在。我们常会遇到“有符号(signed)”和“无符号(unsigned)”变量的概念。它们决定了变量所能表示的数值范围和符号处理方式。本文将系统地解释它们之间的区别,帮助你理解何时使用哪种类型。
2. 一个简单的例子
我们先以一个“4 位”存储系统为例,来理解不同变量类型的表现形式。
一个 n 位的二进制数可以表示 $ 2^n $ 个不同的值。在 4 位系统中,总共有 $ 2^4 = 16 $ 个可能的值。
如果我们将这些值解释为 无符号数,则其范围为 0 到 $ 2^n -1 $,即 0 到 15。
下图展示了 4 位无符号数的所有可能值:
3. 一补码(One’s Complement)
在“一补码”表示法中,最左边的一位作为符号位:
- 若为 0,表示正数;
- 若为 1,表示负数。
对于正数,直接将其余位转换为十进制即可。例如:
0101
表示 +5。
对于负数,我们通过将所有位取反(0 变 1,1 变 0)来得到其绝对值。例如:
1011
表示 -4,因为取反后为0100
,即 4。
3.1 一补码的问题
一补码的一个显著问题是:存在两个零:
0000
表示 +0;1111
表示 -0。
这在早期使用一补码的计算机中是一个棘手的问题,因为程序必须同时处理这两种“零”。现代系统中已很少使用一补码,但在某些特定场景(如 TCP 校验和)中仍需注意。
4. 二补码(Two’s Complement)
二补码是对一补码的改进。其关键区别在于:
- 负数的计算方式是:先取反(一补码),再加 1。
例如:
1010
是负数;- 取反得
0101
; - 加 1 得
0110
,即 6 → 所以1010
表示 -6。
4.1 正数的表示
与一补码相同,正数的符号位为 0,其余位直接转换为十进制即可。
4.2 负数的表示
负数的符号位为 1,求值方法是:
- 取反所有位;
- 加 1;
- 得到绝对值。
4.3 负数范围
在 n 位二补码系统中,负数范围是:
- $ -1 $ 到 $ -2^{n-1} $
在 4 位系统中,范围是 -1 到 -8。
这比一补码多了一个负数(-8),而且只有一个 0,避免了“+0 和 -0”共存的问题。
5. 总结对比
下表总结了无符号、一补码和二补码的表示范围和特点:
5.1 无符号数(Unsigned)
位数 | 范围 | 二进制表示 |
---|---|---|
8 | 0 ~ 255 | 00000000 ~ 11111111 |
16 | 0 ~ 65535 | 00000000 00000000 ~ 11111111 11111111 |
32 | 0 ~ 4294967295 | 四组 11111111 |
n | 0 ~ $ 2^n -1 $ | - |
✅ 特点:只能表示非负数,范围最大。
5.2 一补码(One's Complement)
位数 | 范围 | 二进制表示 |
---|---|---|
8 | -127 ~ +127(含 +0 和 -0) | 10000000 ~ 11111111 和 00000000 ~ 01111111 |
16 | -32767 ~ +32767 | - |
32 | -2147483647 ~ +2147483647 | - |
n | $ -(2^{n-1}-1) $ ~ $ +(2^{n-1}-1) $ | - |
❌ 缺点:存在两个零,易导致判断错误。
5.3 二补码(Two's Complement)
位数 | 范围 | 二进制表示 |
---|---|---|
8 | -128 ~ +127 | 10000000 ~ 11111111 和 00000000 ~ 01111111 |
16 | -32768 ~ +32767 | - |
32 | -2147483648 ~ +2147483647 | - |
n | $ -2^{n-1} $ ~ $ +(2^{n-1}-1) $ | - |
✅ 优点:只有一个零,广泛用于现代计算机系统。
6. 结论
- 无符号变量不能表示负数,但表示范围更大。
- 一补码存在两个零(+0 和 -0),容易造成判断错误,现代已很少使用。
- 二补码是目前最常用的有符号整数表示方式,只有一个零,且负数范围比正数大一位。
- 在实际开发中,如 C/C++、Java 等语言中默认使用二补码表示有符号整数。
📌 踩坑提醒:在进行位操作或跨语言数据传输时,注意不同语言对有符号/无符号的默认处理方式,避免出现数值溢出或符号错误。例如 Java 中没有无符号类型,处理无符号数据时需手动转换。