C 语言中编码整数有两种不同的方式,一种只能表示非负数,也称为无符号数;另一种能够表示负数、零和正数,称为有符号数。而 Java 语言中没有无符号数,所有的整数都是有符号数。
整数数据类型
C 语言支持多种整数数据类型。下图给出了典型的 32 位和 64 位机器上的取值范围。其中 long 类型的取值范围与机器相关。
32 位机器
<img 32-bit src=”/img/hufei/2019/03/32-bit.png” width=”100%” height=”100%”>
64 位机器
<img 64-bit src=”/img/hufei/2019/03/64-bit.png” width=”100%” height=”100%”>
我们注意到 负数和正数的取值范围是不对称的,负数范围比正数范围大 1。
无符号数
假设一个整数数据类型有 w 位,每一位取值为 0 或 1。那么一个 w 位的比特串总共有 2ᵂ 种组合。如果我们把这些组合与整数一一对应起来,我们可以表示 2ᵂ 个整数。其中一种映射关系如下所示。
[0001] = 0x2³ + 0x2² + 0x2¹ + 1x2⁰ = 0 + 0 + 0 + 1 = 1
[0101] = 0x2³ + 1x2² + 0x2¹ + 1x2⁰ = 0 + 4 + 0 + 1 = 5
[1010] = 1x2³ + 0x2² + 1x2¹ + 0x2⁰ = 8 + 0 + 2 + 0 = 10
[1111] = 1x2³ + 1x2² + 1x2¹ + 1x2⁰ = 8 + 4 + 2 + 1 = 15
在整数和比特串之间建立映射关系的过程就是编码的过程。 w 位的二进制数可以表示的无符号整数范围是 0 ~ 2ᵂ-1。
有符号数
不知道是从哪里听到过一种说法,说可以把最高位当做是符号位,这样就能区分正负数了。但是,把最高位当做符号位只是人为规定的,计算机并不知情。
模
在日常生活中,仔细观察会发现
- 把物体左转 90 度和右转 270 度,在不考虑圈数的情况下,最终的效果是相同的。
- 把分针顺时针拨 20 分钟和逆时针拨 40 分钟,在不考虑时针的情况下,效果也是相同的。
- 把数字 87 减去 25 和加上 75,在不考虑百位数的情况下,效果也是相同的。
上述几组数字,有这样的关系:
90 + 270 = 360
20 + 40 = 60
25 + 75 = 100
式中的 360、60 和 100 就是模。假如我们提前就知道模的值,那么就能把减法化为加法。
自定义编码
如果用 8 位二进制数来编码有符号数,2⁸ = 256,那么模就是 256。例如,计算 127 - 1 = ?。利用模的特性,我们可以将减法化为加法。即 127 - 1 <=> 127 + 255。
127 127
- 1 <=> + 255
---- -----
126 256+126,忽略最左边的进位 256,最后结果为 126。
用二进制表示如下
0111 1111 0111 1111
- 0000 0001 <=> + 1111 1111
--------- ---------
0111 1110 1 0111 1110 ,忽略最左边的进位 1,最后结果为 126。
在不考虑进位的前提下,不难发现 -1 和 [1111 1111] 之间存在着一种映射关系。假如我们按如下的方式来编码 8 位二进制数:
二进制数 | 十进制数 |
---|---|
1000 0000 | -128 |
1000 0001 | -127 |
1000 0010 | -126 |
1000 0011 | -125 |
… | … |
1111 1101 | -3 |
1111 1110 | -2 |
1111 1111 | -1 |
0000 0000 | 0 |
0000 0001 | 1 |
0000 0010 | 2 |
… | … |
0111 1101 | 125 |
0111 1110 | 126 |
0111 1111 | 127 |
在得到 [-128, 127] 的二进制编码的同时,又将减法运算化为了加法运算。这种编码方式,叫做补码(two’s complement),这是最常见的有符号数在计算机中的表示方式。