声明:这里所有知识都来自《深入理解计算机系统》——第2章 信息的表示和处理
首先计算机和编译器支持多种不同方式编码的数字格式,补码编码是其中的一种,其他的比如ASCII、Unicode也都是编码方式。而补码作为一种编码方式,它本身的原理与按位取反没关系。在介绍它的编码原理之前,理解编码这一概念有助于我们加深对补码的认识?
1、为什么需要编码
计算机存储和处理的是二进制数字(或称为位),编码决定了这些二进制数据会怎么被处理,即编码规定了数据的处理规则。
一个典型的例子:假设在C语言中声明无符号整型unsigned int a=5和和有符号整型int b = 5,对应的都是4个字节,32位的数字,他们的二进制表示是一样的(计算机底层处理的是二进制数),如下
a:0000 0000 0000 0000 0000 0000 0000 0101
b:0000 0000 0000 0000 0000 0000 0000 0101
虽然二进制表示一样,而且数值一样(都是5),但是计算机在处理不同类型的数据时,使用的是不同的编码方式。
同样的,假如在C语言中声明一个字符char c = 48(与char c = '0’具有同样的效果),对应的二进制数是:
c:0001 1000
这些二进制数看起来都类似,但是通过声明数据的类型,编译器和计算机就能够知道怎么处理这些二进制数。
2、补码
为方便阐述,将二进制数理解为位向量 x =
[
x
w
−
1
,
x
w
−
2
,
.
.
.
,
x
0
]
[x_{w-1}, x_{w-2}, ..., x_{0}]
[xw−1,xw−2,...,x0] (向量这种专业词有点唬人,其实就是把二进制数看成了一些有序的数的集合)。
补码编码的定义: (见中文第3版45页)
对于向量x =
[
x
w
−
1
,
x
w
−
2
,
.
.
.
,
x
0
]
[x_{w-1}, x_{w-2}, ..., x_{0}]
[xw−1,xw−2,...,x0](即一串二进制数):
他的值为:
f(x) =
−
x
w
−
1
2
w
−
1
+
∑
i
=
0
w
−
2
x
i
2
i
-x_{w-1}2^{w-1} + \sum_{i=0}^{w-2}x_i2^i
−xw−12w−1+∑i=0w−2xi2i (1)
最高有效位
x
w
−
1
x_{w-1}
xw−1也称符号位。
于是计算机按照这种方式来计算int型变量b的值。
PS:有些时候可能会听到按位取反的说法,比如按位取反的理解一文中的阐述。但我们要明白的一点是:这种说法只是为了我们快速算得二进制与十进制的表示,是一种取巧的做法(这个规律是正确的),但补码编码的最初定义与按位取反无关。
PS:补码主要用于表示有符号数(包含正数、负数和0),无符号数的定义则相对简单(具体定义在下文中)但不能表示负数。此外,曾经也有原码编码、反码编码也能表示有符号数,但是这两种方式都有一个奇怪的属性,就是对于数字0有两种不同的编码方式。目前,有符号整数几乎都用补码方式类编码。
3、无符号数与ASCII编码
为了给出原理(编码方式)的不同,这里也给出无符号数与ASCII编码的编码原理:
无符号数编码定义:
f(x) =
∑
i
=
0
w
−
1
x
i
2
i
\sum_{i=0}^{w-1}x_i2^i
∑i=0w−1xi2i (2)
这个和(1)对比,就能明白为什么补码中1称为符号位,而无符号数没有符号位。
而字符型变量c则是有一个ASCII表来与之一一对应,处理字符的时候二进制数会被处理成表中对应的内容。
最后应该明确的是,上面说的是C语言的规定,Java有不同的规定,这使得Java代码可移植性较好(可以再不同机器上运行,不用重新编译)。