计算机很多术语翻译成中文之后,不知道是译者出于什么目的,往往将其翻译成一个很难懂的名词。
奇怪的数学定义
下面是关于原码的“吐槽”,可以当作扩展。你可以不看,直接去下一章,没有任何影响。
原码的吐槽放在前面是防止读者看完原码,然后看半天才看到补码,影响阅读体验。
某些书描述“原码”的时候很“奇怪”,你可能在某些书上见到过下面这样很难理解的描述(下图截自原码 - 维基百科):
这玩意是数论里的等价类,也就是计算机里常出现的“模运算”。或者还有个更高级的名字:散列(因为很多散列算法就是模运算,然后用的等价类的思想)。
等价类:
3 mod 2
和1 mod 2
的结果都为1
,所以都等于[1]
,也就是3
和1
都是1
的等价类。
那上面的那一堆是什么意思呢?
以整数原码为例,X
是一个数,范围不定。
[+1011]
的意思是和+1011
等价的所有数:
X
m
o
d
2
n
=
1011
X mod 2^n = 1011
Xmod2n=1011
比如
n
>
4
n>4
n>4的时候
2
n
>
0
b
1111
2^n>0b1111
2n>0b1111
1011
m
o
d
2
n
=
1011
(
2
n
+
1011
)
m
o
d
2
n
=
1011
1011 mod 2^n = 1011\\ (2^n + 1011) mod 2^n = 1011
1011mod2n=1011(2n+1011)mod2n=1011
而[-1011]
的意思是和(2^{n-1} - 1011)
等价的所有类:
( 2 n − 1 − X ) m o d 2 n = 1011 (2^{n-1} - X) mod 2^n = 1011 (2n−1−X)mod2n=1011
比如是 8 位的数字:
(
2
8
−
1
−
(
−
1011
)
)
m
o
d
2
n
=
(
10000000
+
1011
)
)
m
o
d
11111111
=
10001011
(2^{8-1} - (-1011)) mod 2^n =\\ (10000000 + 1011)) mod 11111111 = 10001011
(28−1−(−1011))mod2n=(10000000+1011))mod11111111=10001011
这是教材,不是论文,而且一般论文也不用这么说吧?
这种表达方式唯一的好处就是让你理解溢出之后该怎么处理,因为取模运算的值就是溢出后得到的值。
说实话这种表达方式就不应该出现在任何现代教材中,哪怕是很多数学分析的书都不会这样描述数的。我不知道这种用法最早出现在哪里,但是欧美一些常见的计算机组成与设计教材中没有出现过这种用法,也就是说并不是盲目学来的。
不论是“true code”还是“true form”都在英文搜索中难以找到。WiKi “有符号数处理”中,“原码”对应的是“符号及值(sign & magnitude)”,英文版直接就是“Sign–magnitude”。这玩意的在一些教材中的翻译是“符号和幅值表达法”,看看这个名字多清晰和直白。
这个方法读者理解就好,没必要学,后文也完全不使用这个方法,怎么容易理解怎么来。但计算机科学确实有很多算法使用了等价类的概念,所以如果你要走算法方向,还是要看看的。
原码
原码就是我们常用的数,在计算机中就是用二进制表示的十进制数,不论正负、不论整数还是小数。
n
位原码就是用最高位(小端就是最右位)当作符号位(0
为正数,1
为负数),关于0
对称范围内的数(
−
2
n
~
2
n
-2^n~2^n
−2n~2n),比如-4~4
、-16~16
,范围的两个端点关于0
对称,正负数的个数相同。
补码(2’s complement)
n
位补码就是用最高位(小端就是最右位)当作符号位(0
为正数,1
为负数)关于0
不对称范围内的数(
−
2
n
−
1
~
(
2
n
−
1
−
1
)
-2^{n-1}~(2^{n-1}-1)
−2n−1~(2n−1−1)),比如-16~15
,范围的两个端点关于0
不对称。
这里概念很容易和和其他码搞混,比如原码。
但其实只用记住一个关键点:补码的-1
是1111
,而原码是1001
。
换言之,从零值的“生长”来说:
- 原码相当于少一位(最大的那位)的无符号数。抛开符号位
-x
和x
的表达是一样的,所以上下限的绝对值是一样的。 - 补码从
0
开始增减,0b0000-1
,借位算出是0b1111
。但是+1
增的时候,要注意不能让最大位为1
,所以上限比下限绝对值少1
。
补码是现在主流的格式,所以各种考试也主要考补码。所以只讲一下补码的计算。
补码计算的时候,加减法要带着符号位进行,乘除法就是左移补0,右移补符号位
。
比如:
1011*2
,得到的是0110
。1011/2
,得到的是1101
。0011+0011
,也就是3+3=6=0110
0011
+0011
-------
0110
0011-0111
,也就是3-7=-4=1110
。
0011
-0111
-------
1100
用加减法可以很容易得到-x
对应的二进制,反之亦然。
举个例子,我要计算出1111 0100
的的十进制,那么可以先计算出和-1
(1111 1111)的差(前 4 位相同,甚至可以不用写):
0000 0000
-1111 0100
-----------
0000 1100
得到12
,加上符号-
(原二进制符号位为1
,也就是负的),那么最后得到-12
。
反之,我们想计算出-8
的二进制,也就是和0
差8
(0000 1000),那么就可以:
0000 0000
-0000 1000
-----------
1111 1000
是不是很简单。
反码(1’s complement)
反码中,一个数的相反数就是按位取反,1
变0
,0
变1
,这也是名字的由来。
比如1
(00000001)按位取反,得到-1
(11111110)。
所以反码和原码一样,是关于0
对称的,所以正负数的个数相同。但是不同之处在于:反码有两个0
,正0
(0000)和负0
(1111)。
在无符号数的反码中,x
按位取反为
2
n
−
1
−
x
2^n-1-x
2n−1−x。
因为无符号反码增长就是从0
一直加,所以最大值是
2
n
−
1
2^n-1
2n−1。而按位取反就相当于从尾部往前倒。
你可以观察一下下面这个表格,再看看我的说法,你就会理解了。
反码(1’s complement)早期设备用的多,现在用补码(2’s complement)是主流,因为反码计算的时候更麻烦一些,只是在计算一些科学计算的时候更有优势罢了。
看开头的数字也可以看出顺序来(啊对,这两个英文中的数字就是版本,这你能想到哈哈哈哈)。
移码(Offset binary)
移码又称“偏移表示法”,这个方法现在是上面浮点数用的多,比如 IEEE 754。
移码从0000
开始递增,依旧使用最高位位符号为,只不过这里1
表示正数
,0
表示负数。与前面的几种方法相反。
有符号移码最小的负数是0000
,最大的整数是1111
,0
是1000
。
这里可能好奇名字中“偏移”的意思,其实就是“给数加上一个偏移数后,使其具有非负的表达形式”。
比如上面的0
,加上一个偏移1000
,得到的就是1000
,也就是0
,非负。
关于这部分,浮点数中再细说吧。
希望能帮到有需要的人~
参考资料
《Computer Organization and Design MIPS Edition: The Hardware/Software Interface Fifth Edition》:如果你要学习计算机结构的话,这本书要比国内的很多教材好,但是翻译的确实不太行。
原码 - 维基百科
Signed number representations - 维基百科