本科阶段很早就学过原码、反码与补码,但当时没有实际应用过。今天在刷371题时,涉及包含负数的二进制加法,这才开始明白为什么在计算机中负数需要以补码的形式来存储。
在此之前,必须要明白的一个核心概念是同余,这里给出一个引例:
我们有一个挂钟,上面均匀分布着12小时,现在时针指向的时间是3点,假如我们要使时针指向5点,需要怎么做呢?
比较容易想到的是2种做法:
- 将时针逆时针拨10小时
- 将时针顺时针拨2小时
这个过程其实就是一个取余运算,相当于:
(3-10)%12 = 5
(3+2)%12 = 5
这种情况下,可以说2与-10是同余的,即当模为12时,一个数-10相当于+3的过程,因为超出12的那些部分都被求余数过程给“抹掉”了。
二进制加法器就是基于这样的思想。与上例对应地,计算机将1Byte(8bit)作为一个最基本的内存单元。
在有符号整数中,最高位为符号位,8bit中只有7bit用来保存数据,所以1Byte可以表示的有符号整数范围为-127 ~ 127(即11111111 ~ 01111111)。(不过大家都知道,通常都说1Byte表示的有符号整数范围是-128 ~ 127,这里给出一篇参考文章,由于内容不涉及,故不作赘述。)因此,对应于钟表模12,这里模的是128(后7个bit满128时归0)。肯定会有人问,那高位进1去哪里了,由于只有7位用于储存数值,所以这里产生了一个事件,叫做溢出,溢出的1将被抛弃。就像钟表顺时针转14小时时,其实是有1次溢出的,但是我们仅仅关注表盘上能够呈现的时间,所以从3点转14小时仍然是5点。计算机内存中的溢出,相当于引例中所说的“抹掉”的过程。
基于以上类比,我们可以得出规律:
在模为12的钟表上让3-10,相当于让3+2
等价于
在模为128的8bit内存单元中,一个数A加一个负数B,相当于数A加一个与B同模的正数C
负数的补码就是负数原码对应的那个同余正数的原码,不过我们需要忽略这个数的“符号位”,因为“符号位”的本质其实并不是标识符号,而是为最终的溢出所服务。
举例来说,-3的原码是 1000 0011,我们都知道其反码是 1111 1100,补码是1111 1101,将该补码看作一个正数的原码,忽视其符号位,其实这个数就是125,但它的最高位被置为了1,就像上面说的,这是为了在加法中产生溢出。
例如计算20-3=17,用-3的补码进行加法,其实可以看作20+125%128=17(只要不把符号位看在眼里就行):
0001 0100 + 1111 1101
=(1)0001 0001
丢弃溢出的1,得到的结果是0001 0001。值得注意的是,二进制加法是补码运算,运算结果也是补码,所以需要转为原码。由于这里的结果是一个正数,原码=补码,因此可以得到其结果是17。(若为负数需要按步骤转换成原码再读数)
通过以上的演示,我想对于编程中为什么会产生数值的溢出,大家应该有了一定的理解——那便是计算的结果不能为当前长度的内存单元所容纳,必须采用比特位更多的内存单元来存储最高位的进位等。因此,在书写代码时,也务必要按照需求将数值定义成合适的类型,保证其不溢出。
我只会html 发布了47 篇原创文章 · 获赞 0 · 访问量 560 私信 关注