原码 反码 补码
机器数和真值
机器数
一个数在计算机中的二进制表示形式, 叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1 。
比如,十进制中的数+3
,计算机字长为8位,转换成二进制就是00000011
。如果是 -3
,就是 10000011
。
那么,这里的 00000011
和 10000011
就是机器数。
真值
因为第一位是符号位,所以机器数的形式值就不等于真正的数值。例如上面的有符号数 10000011
,其最高位1
代表负,其真正数值是 -3
而不是形式值131
(10000011
转换成十进制等于131
)。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。
因此10000011
的真值为-3
。
原码 反码 补码
原码
原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:
[-1]原 = 1000 0001
[+1]原 = 0000 0001
第一位是符号位。因为第一位是符号位, 所以8位二进制数的取值范围就是:
[1111 1111 , 0111 1111]
即
[-127, 127]
反码
反码的表示方法是:
正数的反码是其本身
负数的反码是在其原码的基础上,符号位不变,其余各个位取反:
[+1] = [00000001]原 = [00000001]反
[-1] = [10000001]原 = [11111110]反
可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值。通常要将其转换成原码再计算。
补码
补码的表示方法是:
正数的补码就是其本身
负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后加1(即在反码的基础上加1)
[+1] = [00000001]原 = [00000001]反 = [00000001]补
[-1] = [10000001]原 = [11111110]反 = [11111111]补
为什么需要反码和补码
首先, 因为人脑可以知道第一位是符号位, 在计算的时候我们会根据符号位, 选择对真值区域的加减。 但是对于计算机, 加减乘数已经是最基础的运算, 要设计的尽量简单 。 计算机辨别”符号位”显然会让计算机的基础电路设计变得十分复杂 。 于是人们想出了将符号位也参与运算的方法 。
我们知道, 根据运算法则减去一个正数等于加上一个负数, 即: 1 - 1 = 1 + (-1) = 0
, 所以机器可以只有加法而没有减法, 这样计算机运算的设计就更简单了 。
对于计算1-1=0
,首先来看原码:
1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2
如果用原码来进行计算,将符号位也参与计算,显然计算结果是错误的。
因此引入了反码:
1 - 1 = 1 + (-1) = [00000001]反 + [111111110]反 = [11111111]反 = -0
发现使用反码计算,结果的真值是正确的。但是也存在问题,再看下面一个例子:
0 + 0 = [00000000]反 + [00000000]反 = +0
对于结果0
,可能会得到-0
和+0
两个不同的结果。
而补码的出现,解决了这个问题:
1 - 1 = [00000001]补 + [11111111]补 = [00000000]补 = 0
0 + 0 = [00000000]补 + [00000000]补 = 0
可以看到,使用补码进行计算,将会只有一个0
,而且可以用10000000
来表示-128
:
-1 + -127 = [11111111]补 + [10000001]补 = [10000000]补
因此,对于一个8位的带符号数,如果使用补码来进行表示,则取值范围位[-128, 127]
而对于原码和反码,因为都存在两个0
:
+0 = [00000000]原 = [00000000]反
-0 = [10000000]原 = [11111111]反
有效取值范围都只有[-127, 127]
使用补码,不仅解决了减法的问题,还修复了存在两个0
的问题,使得负数的有效值可以多一位。
因此,计算机都是使用补码来表示正数,对于n
位的整数,其有效范围为:
[-2n-1, 2n-1-1]
补码的本质
补码本质是用来构成一个环,以实现一个同余运算。
假设8位的整数为例,我们计算的结果最终都是2^8
的余数,这与符号位没有关系,也就是需要mod 2^8
,也就是当计算结果超过8位时,高位会被舍弃,就是溢出。
比如:
1 + 255 = 256 = 0
1 - 1 = 0
这其实是同余的概念:两个整数a,b,若它们除以整数m所得的余数相等,则称a,b对于模m同余,记作a ≡ b (mod m)
我们来看 255和-1:
255 mod 256 = 255
-1 mod 256 = 255
也就是 -1 ≡ 255 mod 256
在一个环中,00000000b和11111111b连接,当00000000b向后走一步,就回到11111111b了。
同余加法:
a ≡ b mod m
c ≡ d mod m
a+c ≡ b+d mod m
所以有:
1 ≡ 1 mod 256
-1 ≡ 255 mod 256
1-1 ≡ 1+255 mod 256
所以 1-1 = 1+255 = 0
对于255的二进制表示:
255 = 11111111b
因为 -1≡255 mod 256
,在8位的带符号数中,我们可以用11111111b来表示-1
,而这不正是-1
的补码吗。
应用
前面讲到,计算机中使用补码存储整数,而我们可以把补码看成是一个环
因此:1
2
3a := uint8(2)
b := uint8(255)
a - b == 3 // 2倒退3步回到255
利用这个性质,当我们在实现环形队列时:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38type RingQueue struct {
buf []interface{}
head, tail uint32
cap uint32
}
func New(cap uint32) *RingQueue {
return &RingQueue{
buf: make([]interface{}, cap),
cap: cap,
}
}
func (r *RingQueue) Push(v interface{}) bool {
if r.tail-r.head < r.cap {
r.buf[r.tail%r.cap] = v
r.tail++
return true
}
return false
}
func (r *RingQueue) Size() uint32 {
return r.tail - r.head
}
func (r *RingQueue) Cap() uint32 {
return r.cap
}
func (r *RingQueue) Pop() (interface{}, bool) {
if r.tail-r.head > 0 {
v := r.buf[r.head%r.cap]
r.head++
return v, true
}
return nil, false
}