计算机的底层是一堆的bit,具体怎么理解bit,取决于我们怎么解释,对于同样的8个bit,例如 11111111
,我们可以把它看成是一个无符号数,也可以看成是一个有符号数。
如果我们把它看成是一个无符号数,那么它的值就是255,如果我们把它看成是一个有符号数,那么它的值就是-1, 如果理解为字符,那么它就是一个字符,具体是什么字符,取决于编码方式,如果是ASCII编码,那么它就是DEL字符。
对于无符号数,我们可以直接用二进制表示,例如 11111111
就是255,计算机很轻易就能理解二进制,但是人类比较熟悉十进制。我们看看换算过程:
1*2^7 + 1*2^6 + 1*2^5 + 1*2^4 + 1*2^3 + 1*2^2 + 1*2^1 + 1*2^0 = 255
但是现实情况中,数字不仅有正数,还有负数,那么我们怎么表示负数呢?我们可以用补码的方式来表示负数。
原码是最简单的一种表示方法,就是用最高位来表示符号,0表示正数,1表示负数,其余位表示数值。例如,00000001
表示1,10000001
表示-1。
原码的优点是,非常直观,可以直接看出一个数是正数还是负数,但是原码的缺点也很明显,就是加减法非常麻烦,因为要考虑符号位。
比如我们要计算1+1,首先我们要找到1的原码,然后再相加,最后再判断符号位。
1的原码:00000001
1的原码:00000001
相加:00000010
换算成十进制:2
但是,如果我们要计算1-1,首先我们要找到1的原码,然后找到-1的原码,然后再相加,最后再判断符号位。
1的原码:00000001
-1的原码:10000001
相加:10000010
换算成十进制:-2
可以看到,计算结果是错误的,这是因为原码的计算中,符号位也参与了计算,导致结果错误。
因此有了反码的出现。
反码是在原码的基础上,对负数的数值部分取反。例如,00000001
表示1,11111110
表示-1。
反码的优点是,加减法非常简单,只需要把两个数的反码相加即可,不需要考虑符号位。
比如我们要计算1+1,首先我们要找到1的反码,然后再相加,最后再判断符号位。
1的反码:00000001
1的反码:00000001
相加:00000010
换算成十进制:2
如果我们要计算1-1,首先我们要找到1的反码,然后找到-1的反码,再相加,最后再判断符号位。
1的反码:00000001
-1的反码:11111110
相加:11111111
换算成十进制:-0
但是反码的缺点也很明显,就是有两种0,正0和负0,这样会导致一些问题,因此有了补码的出现。
补码是一种用来表示负数的方法,它的特点是,正数的补码就是它本身,负数的补码是它的绝对值的二进制取反加1。
例如,我们要表示-1,首先我们要找到1的二进制表示,然后取反,最后加1,就是-1的补码。
1的二进制表示:00000001
取反:11111110
加1:11111111
所以-1的补码是11111111,同时,我们可以看出,2的补码有以下优点:
可以看到,补码的形式中,只有一个0,所以不会出现正0和负0的问题,这样就避免了一种情况,同时,两个数相加和相减的操作也变得非常简单,只需要把补码相加即可,简化了硬件的实现。因此, 补码才会被广泛选择作为表示负数的方法。
记得初学计算机的时候,觉得补码难以理解,工作以后,才慢慢理解,现实情况中,我们往往会因为实际情况拖鞋,补码就是如此,舍弃了易理解性,但是提高了计算的效率,这就是现实情况中的折中。
软件工程中,到处都是取舍,我们要根据实际情况,选择最合适的方法,往往我们都找不到完美的方案,但是却有一个最合适的方案。