一、引子
经过之前的学习,我们已经知道了怎么把人类熟悉的十进制数转换成计算机方便处理的二进制数,以及二进制、八进制、十六进制之间如何转换。
如果进制转换,自己觉得掌握的还不够熟练的同学,可以去百度搜索一下在线进制转换,有好几个网站可以给你提供进制转换这样的一个功能。
你可以自己给自己出题,写一个你喜欢的十进制数,自己先手算一下,再用网站去验证一下你的手算的结果是否正确,用这样的方式来自我训练。
现在我就默认大家已经非常熟悉十进制和二进制之间的转换了。
在这个小节中,我们会进一步的学习无符号的整数在计算机内部应该如何表示和运算。
所谓的无符号整数
,指的就是数学里边的自然数,也就是01234。
在 c 语言当中,我们也会经常用到无符号的整数,只要使用 unsigned
关键字,就可以定义一个无符号整数。
比如 unsigned short,它是两个字节,短整形的无符号整数。 unsigned int,它是 4 个字节,是整形的无符号整数。
前边 unsigned ,规定了有没有正负性,而后边 short 和 Int 是规定了无符号整数的长度总共是多大,占多少个字节。 这是 c 语言里边的无负号整数。
接下来我们要探讨这样的两个问题,无符号整数在计算机硬件里边,它是如何表示的?
第二个问题,无符号整数的加法和减法运算是怎么用硬件实现的?
这就是这个小节的两个主题。
二、无符号整数在计算机内部的表示
(1)介绍
首先我们来看无符号整数在计算机内部的表示。
之前我们说过,如果一个计算机,它的机器字长是8位,这就意味着这个计算机它同时只能处理8位的运算。
比如 a 和 b 两个数分别是 8 个比特,所以机器字长它限定了每次可以进行多少个比特位的运算。
除此之外,机器字长也限制了计算机内部它的通用寄存器总共有多少位,它的长度是多少。
像上图的计算机,它内部的通用计存器只能存8位的数据和信息。
所以计算机硬件能支持的无符号整数,它的位数是有上限的,是由机器字长来限制的。
在这个小节中,我们会以机器字长为8位的这样的一个前提条件,来探讨无负号整数的表示和运算。因为8位的长度比较方便我们探讨。
现在的计算机,机器字长通常是 64 位,或者至少也应该是 32 位。不管机器字长是多少位,它的原理都是相同的。
(2)例子
现在我们来看,在一个机器字长为8位的计算机内部,无符号整数应该如何表示。
我们直接来看几个例子。
①真值0
对于真值0,二进制的表示也是0,但是把它放到计算机内部的时候,需要把它扩展为和机器字长相同的一个长度,也就是要扩展为 8 个比特。因此更高的位需要全部补0。
所以无符号数 0 放到机器里边是这个样子。
②真值99
接下来无符号整数99,先把它转换成二进制数,总共有 7 个比特,放到 8 比特的寄存器里边,需要在高位补一个0。
③真值255
接下来255,总共 8 个比特,刚好可以放到8比特的寄存器里边。
④真值256
最后这个 256 无负号整数。如果用二进制表示,应该是一个 1 加上 8 个0。 如果把这个数强行塞到8比特的寄存器里边,只能保存更低的 8 个比特,最高的这一个有效信息就溢出丢失了。
所以这就意味着计算机硬件可以表示和处理的无负号整数,它一定是有一个范围,有一个上限的。
如果你硬要把一个很大的数强行塞到寄存器里边,那么计算机的处理方式就是,只保留更低 n 个比特。
所以强行硬塞会导致数据信息的丢失。
在计算机内部表示一个无符号数的时候,每一个比特位,它都对应了一个位权的信息。
比如最高的比特位,它的位权就是 2 的 7 次方;次高的比特位,它的位权就是 2 的 6 次方;而最低的比特位,它的位权就是 2 的 0 次方。
所有的这 8 个比特,表示的都是数值信息,并没有所谓的符号位,因为它本身就没有正负这一说。
刚才我们也说到 n 比特的无负号整数,它有一个合法的表示范围是 0~2^n-1
。如果超出这个范围,就发生了溢出,这就意味着这台
计算机它无法一次处理这么多的数据。
n 比特无符号整数,它可以表示的范围是 0到2 的 n 次方减1。
这个是怎么来的?很简单,首先它可以表示的最小的数肯定是全0,全0表示的就是真值0;而它可以表示的最大的数是全1,所有的比特都是1,它对应的真值应该是多少?应该是 2 的 0 次方加 2 的一次方,一直加到最高位 2 的 n 次方减 1 。
所以这就是一个简单的等比数列求和问题,求和的值就是 2 的 n 次方减1。
这就是 n 比特的无符号整数所能表示的最大的范围。
这是无符号数的表示。
三、运算
接下来我们要探讨的是无符号整数的加法和减法运算在计算机内部是如何实现的。
(1)加法
首先来看加法运算
实现的方法,从最低位开始按位相加,并且往更高位产生进位的信息。
其实就是我们最熟悉的加法竖式的处理方式,我们来看一下。
比如下面的例子中,A+B是多少呢?
从最低位开始,按位相加, 1 + 1 = 2,由于是二进制,所以逢 2 需要进1,因此本位和是0,往更高位进 1 。如下:
更高位也按位进行相加, 1 + 0再加刚才进的 1 等于 2,同样的逢 2 进1。本位留0,往更高位进 1 。如下:
更高位 0 + 0再加 1 等于1,这一位往更高位的进位应该是 0 ,没有进位。如下:
再更高位 0 + 1再加 0 等于 1,以此类推。
所有的这些位从低到高依次按位相加,同时往更高位产生进位。
用这样的方式,我们就可以得到两个无符号整数相加的一个结果, 那99 + 9 应该是等于108。
大家可以自己验证一下,用二进制表示的这个值是正确的,这是无符号整数的加法实现的原理。
(2)减法
接下来看无符号整数的减法
应该如何实现。
同样的,用 99 减掉9,我们用十进制先来算一下,应该是等于90。
我们最终期待的结果应该是这样的一个值,这是 90 的二进制,表示也就是两个数相减之后,要等于这样的一个值。
如果用我们人类的算法,是不是应该列竖式,从最低位按比特依次的往前减对吧? 1- 1 = 0,1- 0 = 1,用这样的方式往前减,如果不够紧,就从更高位借位,这是我们手算的方式。
对于计算机硬件来说,它实现减法操作不是用我们的这种方法来做的。
它是怎么做的?
首先, A 是被减数, B 是减数。
计算机实现减法的步骤是这样的,被减数不变,也就是 A 的值保持不变。
减数要进行特殊的处理,全部位按位取反然后末位加1。
把减法变成加法,最后再用之前介绍的加法规则,从最低位开始按位相加,并且往更高位进行进位。
:question: 为什么要用这样的骚操作把减法变成加法?
原因是加法电路它的造价便宜,而减法电路的造价更昂贵。所以如果我们能把减法的操作,转换成等价的加法操作,就可以省钱,计
算机硬件的制造成本就能更低。
现在我们不去探讨为什么这么做,可以把减法转变为等价的加法。 要解释清楚这个问题,需要结合数论的知识才可以聊清楚。所以在这门课程当中,不建议大家去纠结为什么要这么做,大家只需要记住计算机就是这么做的就可以了。
接下来我们来看一下第一步具体是怎么实现的。
<1> 要实现 A 减 B 的功能,首先需要把减数全部按位取反,并且末位加1。
什么叫按位取反?就是把 B 这个减数,它的 1 变 0、0 变1,所有的比特位都这么处理。如下:
全部位按位取反之后,末位还需要加一个1,加 1 之后的结果就是这个样子。
具体的算法和之前介绍的加法是一样的,从低位到高位一个一个相加就可以了。
这就是减数B 的变形。
<2> 现在我们把减数B变形了之后,就可以把 A 减 B的 减法操作转换成等价的加法操作,变成A加上 B 的变形。
刚才我们把 B 这个减数通过全部取反,末尾加 1 进行了转换,变成了减数 B 的一个变形。
接下来减数变加法,用被减数A的值加上刚才我们变形得来的值就可以了。
<3> 接下来进行加法的处理。
从最低位开始,按位相加, 1 + 1 = 0,往更高位进一个1。
3 个 1 相加,等于 3, 逢 2 进1,往更高位进一个1。本位留下一个 1(3- 2 = 1),所以本位需要留下一个1。
再往前一位, 0 + 1再加1,本位留一个0,往更高位进1。
接下来这一位, 0 + 0再加 1 等于 1;再往前 0 + 1 等于 1;再往前 1 + 1 等于 0,往更高位进1;再往前 1 + 1再加1,本位留一个1,往更高位进一个1;再往前 1 + 1 = 0,往更高位进一个1。
这就是刚才加法的结果。
刚才的加法最后的运算结果有 9 个比特,但是我们只能保留最低的 8 个比特,最高位的比特会被我们丢弃,它不影响我们的计算结果。
你看一下01011010这个数是不是刚好是90。
再梳理一遍这个过程。
无符号整数的减法运算,在计算机的视角它是这么处理的。被减数保持不变,减数进行特殊的处理。全部位按位取反,末位加1。
用处理之后的数和原先的被减数进行加法操作。加法的效果等价于之前的 A 减B。从最低位开始按位相加,并且需要考虑往更高位的进位信息。
用这种方式就可以实现减法运算。
还是那句话,在机组这门课里边,大家只需要记住它是怎么做的就可以。
至于为什么这么做,有兴趣的同学可以去研究一下数论。为了减轻大家的学习负担,这就不再讲 why 的问题了,大家只需要管how怎么做就可以了。
四、回顾总结
以上这就是无符号整数大家需要注意的一些问题。
特别强调关注这样的两个点。
首先,无符号整数 n 个比特的无符号整数,它的表示范围是多大,以及两个无符号整数的减法运算计算机是怎么实现的,需要重点关注和理解这两个问题。
好的,大家可以自己再回顾一下。
以上就是这小节的全部内容。