CSAPP(二)上——整数表示 信息的表示和处理

整数表示#

无符号#

无符号整数就是正整数,它很简单,不用考虑负数该如何存储。

如果你用w位来存储一个无符号整数,如向量x=[xw1,xw2,...,x0],那么你可以存储[0,2w1]个整数。

我们定义一个函数来表示一个位向量B到一个无符号整数U的映射。

B2Uw(x)=Σi=0w1xi2i

有符号#

有符号整数就是有正有负的整数。电脑肯定不认识负号,所以需要一种以纯数字的形式来表示正负的手段,补码是其中的一种手段,并且使用最广,下面介绍的都是补码,下面当我说无符号数并且没有特殊说明时,说的就是补码。

先看补码是如何将之前的无符号数变成可以表示负数的整数的。它把本来的[0,2w1]之间的2w个数平均分成了2w1个负数和2w1个非负数。但由于零是非负数,所以能表示的正整数自然比负整数少了一个。

最终,w位补码数能表示的整数范围为[2w1,2w11]

上面只是介绍了补码数最终所达到的效果和能够表示的整数范围,但实际上如何表示的还没说。补码数采取的办法很简单,就是将w位之中的最高位加上了一个1的权重。于是我们可以这样定义函数B2T

B2Tw(x)=xw12w1Σi=0w2xi2i

考虑4位补码数[1000],它是四位补码数中最小的一个,因为除了一个负权之外,什么都没有,它的值是23=8。考虑[0111],它是四位补码数中最大的一个,因为它没有负权,完全由w1位的纯正数相加而成,它的值为2w11=7

无符号有符号转换#

规则就是位值不变,只变解释方式。

同样的二进制表示[1000],使用无符号解释方式就是8,使用有符号解释方式就是-8。

所以我们可以定义无符号数U转有符号数T的函数U2Tw(x)=B2Tw(U2Bw(x)),即先将对应的无符号数转成位表示,再用有符号的方式解释它。有符号转无符号函数T2Uw(x)=B2Uw(T2Bw(x))

上面的转换公式虽然正确但却有点麻烦,无论如何都需要将一种表示转换成位表示,再将位表示转换成另一种表示。实际上,相同的位,用无符号数和有符号数来解释唯一不同的地方就是最高位究竟是否具有负权,剩下都相同,所以我们可以导出:

B2Uw(x)B2Tw(x)=xw12w1(xw12w1)=xw1(2w1+2w1)=xw12w

也就是说,在同样的位表示下,你用无符号和有符号数的方式来解释,它们中间差了xw12w,所以:

T2Uw(x)=x+xw12wU2Tw(x)=xxw12w

整数扩展#

整数扩展即把一个w位的整数换用w位来表示,w<w。在C语言中你可以认为是shortint转换。

无符号数扩展#

只需要在前面补零即可

有符号数扩展#

使用补码的有符号数扩展时,假设原来是w位,x=[xw1,xw2,...,x0],扩大k位时,可以这样表示:x=[xw1,xw1,xw1,...,xw2,...,x0]。即把前面填满xw1,填k次。

我们需要证明B2Tw+k(x)=B2Tw(x),其实只需要证明扩展一位时补码数值不会发生变化即可证明k为任意正整数时值都不会发生变化。

B2Tw+1(x)=[xw1,xw1,xw2,...,x0]=xw12w+xw12w1+Σi=0w2xi2i=xw1(2w12w)+Σi=0w2xi2i=xw12w1+Σi=0w2xi2i=B2Tw(x)

整数截断#

整数截断即把一个w位的整数换用w位来表示,w>w。在C语言中你可以认为是intshort转换。

无论是有符号还是无符号,截断操作都是直接砍掉多余部分。

无符号整数截断#

如果你想把一个无符号数x缩小成k位,那么得到的结果x=xmod2k。因为截断操作是直接砍掉多余的高位,除了剩下的02k1位之外,剩下那些高位去余2k得到的都是0,所以B2Uk(x)=B2Uw(x)mod2k

有符号数截断#

基于补码的有符号数x进行截断时也是直接截掉高位,所以和无符号一样,所以x=U2Tk(xmod2k)(这里假设x是无符号整数了)

整数加法#

两个整数相加,主要看如果结果超出了当前位数所能表示的范围时如何处理,这种情况称作溢出

溢出的处理很简单,就是截断多余的位,下面主要研究截断之后得出的结果与数学中的加法运算结果的差异

下面,符号x+wu代表无符号加法,符号x+wt代表有符号加法,符号+代表数学中的加法

无符号整数加法#

无符号数的溢出处理很简单,因为两数相加,由于进位,最多可能导致和比两个数的位数多1位,所以当发现溢出时,直接把由于溢出而产生的最高位贡献的值2w给去掉就好了。

x+wuy={x+y,if x+y<2wx+y2w,if 2wx+y<2w+1

由于只是简单的截断,无符号数加法也相当于x+wuy=x+ymod2w

溢出会导致结果小于任何一个数

有符号整数加法#

基于补码的有符号的稍微复杂点,因为它要处理两个边界,两个负数相加可能导致向负边界溢出,两个正数相加有可能导致向正边界溢出,而截断后与预期结果的差异在正溢出和负溢出时是不同的。

下面是公式T.a

x+wty={x+y,if 2w1x+y<2w1x+y2w,if 2w1x+yx+y+2w,if x+y<2w1

正溢出,假设w=4,x=[0110]2=6,y=[0101]2=5,数学上,x+y=11,发生了溢出,位表示上是[1011],这里虽然不用截断,但如果按照补码数来解释这个位表示,结果则是8+2+1=5,与之前的11相比,正好相当于减去2w=16

再举个负边界溢出的例子,假设w=4,x=[1010]2=6,y=[1011]=5,此时数学上x+y=11,位表示上是[10101],但需要截断最高位,所以相当于最终结果是[0101]=5,也就是相当于将最高位的负权去掉了,也就是加上了2w

下面进行一些小推导,由于在整个运算过程中,位表示也是不变的,截断也是不变的,变得还只是运算,截断后解释这些位的方式,所以

x+wty=U2Tw(T2Uw(x)+wuT2Uw(y))

利用一些之前的理论对这个进行化简,消除这些位转换,下面是公式T.b

x+wty=U2Tw(T2Uw(x)+wuT2Uw(y))=U2Tw(x+xw12w+wuy+yw12w)=U2Tw((x+xw12w+y+yw12w)mod2w)=U2Tw((x+y)mod2w)

  1. 转换成无符号加法
  2. 使用T2Uw(x)=x+xw12w这个公式
  3. 转换成普通加法,注意转换成普通加法需要一个mod2w
  4. 将具有2w系数的项去除得到最终表达式

通过逻辑推导从理性上理解后,我们还需要感性的理解来方便记忆这个公式,查看公式T.a,无论是对于正溢出还是负溢出还是正常运算,我们都要直接把x加上y,然后如果产生了位扩展(这时是负溢出)就通过mod运算截断,保证始终是w位,如果没产生,那么mod操作也相当于没做,然后再用有符号的方式来解释这个数。

负溢出会导致相加后结果大于任何一个数,正溢出会导致结果小于任何一个数

整数乘法#

不管是无符号还是有符号,两个w位整数相乘所得到的范围可能需要最多由2w个位来表示,所以需要截断

无符号整数乘法#

xwuy=(x×y)mod2w

有符号整数乘法#

xwty=U2T(x×ymod2w)

乘以常数#

因为整数乘法的指令在大多数CPU上要比加法、减法、位运算和移位慢,所以当你编写乘以常数的代码时,编译器经常会将其优化为一些移位操作。

如果要乘以的数是2的幂,那么其效果相当于左移幂次位。而对于任意数,比如x*14,14可能被分解为2^3+2^2+2^1,所以,乘法操作可能会被重写成(x<<3) + (x<<2) + (x<<1),也可能被分解成2^4-2^1

除以常数#

除法不用考虑溢出问题,所以这里我们只考虑除以常数时的一些优化手段。通常编译器也会将除法编译成移位操作和加减操作的结合。

除法会引入一个新的问题,就是可能会出现小数,需要按照一定规则进行取整,对于无符号数,除法会向下取整,对于有符号数,取整会向0的一端靠拢,即正数向下取,负数向上取。

无符号除以2的幂#

对于无符号除法,由于不会产生负数,所以只需要逻辑右移即可。逻辑右移不会处理符号的问题。而且当你对无符号数进行逻辑右移时,确实会向下取整。

有符号除以2的幂#

对于有符号除法,首先需要先考虑符号问题,所以需要使用算数右移,算数右移会复制原始的xw1位作为右移最高端产生的新位。

如果你把一个w位的向量进行逻辑右移3次,那么得到的就是:

[xw1,xw1,xw1,xw1,xw2,...,x3]

然后第二个要考虑的问题就是,如果直接右移只会向下取整,当遇到负号时,比如5.2,会取整成6,这不符合有符号整数除法的定义,有符号整数除法会向0取整。

考虑整数x,y(y>0)x/y=(x+y1)/y,移位操作x<<kx相当于被除数,2k相当于除数。所以当除法的结果是负数时,我们只需要在移位之前对对x加上一个2k1偏置即可让其向上取整。

posted @   yudoge  阅读(177)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示
主题色彩