信息的表示和处理之整数运算

整数运算

有时候我们会发现,两个正数相加会得到一个负数,而表达式x<y和x-y<0的结果不同,这些属性是由于计算机运算的有限性造成的。

无符号加法

考虑两个非负整数x和y,满足0<=x,y<2w。每个数字都能表示为w位的无符号数字。然而,计算它们的和,我们可能得到一个范围在0到2w+1-2的数字。表示整个和可能需要w+1位。例如,图2-16展示了当x和y有4位表示时,函数x+y的坐标图:

图2-16   整数加法。对于一个4位的字长,其和可能需要5位

显示在水平轴上的参数的取值范围为0~15,但是其和的取值范围为0~30。函数的形状是一个有坡度的平面。如果保持和为w+1位的数字,并且把它加上另外一个数值,我们可能需要w+2位,以此类推。这种持续的“字长膨胀”意味着,要想完整地表达算数运算的结果,我们不能对字长多任何的限制。一些编程语言,例如Lisp,实际上就支持无限精度的运算,允许任意在机器内存限制之内的整数运算。

让我们来为参数x和y定义运算,其中0<=x,y<2w,该操作是把整数和x+y截断为w位得到的结果,再把这个结果看作一个无符号数。这可以被视为一种形式的模运算,对x+y的位级表示,简单丢弃任何权重大于2w-1的位就可以计算出和模2w。比如,考虑一个4位数字表示,x=9和y=12的位分别表示为[1001]和[1100]。它们的和是21,5位的表示为[10101]。但是如果丢弃最高位,我们就得到[0101],也就是十进制的5。这就和值21 mod 16=5一致。

我们可以将操作描述为:

原理:无符号数加法

对于满足0<=x,y<2w的x和y有:

图2-17说明了公式(2.11)的这两种情况,左边的和x+y映射到右边的无符号w位的和。正常情况下x+y的值保持不变,而溢出的情况则是该相加的和减去2w的结果。

图2-17   整数加法和无符号数加法间的关系。当x+y大于2w-1时,其和溢出

推导:无符号数加法

如果x+y<2w,和的w+1位表示中的最高位会等于0,因此丢弃它不会改变这个数的值。另一方面,如果2w<=x+y<2w+1,和的w+1位表示中的最高位会等于1,因此丢弃它就相当于从和中减去2w

算数运算溢出,是指完整的整数结果不能存放到数据类型的字长限制中。如等式(2.11)所示,当两个运算数的和为2w或者更大时,就发生了溢出。图2-18展示了字长w=4的无符号加法函数的坐标图。这个和是按模24=16计算的。当x+y<16时,没有溢出,并且就是x+y。这对应图中标记为正常的斜面。当x+y>=16时,加法溢出,结果相当于从和中减去16。这对应图中标记溢出的斜面。

图2-18   无符号加法(4位字长,加法时模16的)

当执行C程序时,不会将溢出作为错误而发出信号,不过有时候我们会希望判定是否发生了溢出。

原理:检测无符号数加法中的溢出

对于范围0<=x,y<=UMaxw中的x和y,令。则对计算s,当且仅当s<x或s<y时,发生溢出。

推导:检测无符号数加法的溢出

通过观察发现x+y>=x,因此如果s没有溢出,我们能够肯定s>=x。另一方面,如果s确实溢出了,我们就有s=x+y-2w。假设y<2w,我们就有y-2w<0,因此s=x+(y-2w)<x。

原理:无符号数求反

对于满足0<=x<2w的任意x,其w位的无符号逆元由下式给出:

推导:无符号数求反

当x=0时,加法逆元显然是0。对于x>0,考虑值2w-x。我们观察到这个数字在0<2w-x<2w范围之内,并且(x+2w-x) mod 2w=2w mod 2w=0。因此,它就是x在下的逆元。

补码加法

对于补码加法,我们必须确定当结果太大(正溢出)或太小(负溢出)时,应该做些什么。给定范围-2w-1<=x,y<=2w-1-1之内的整数值x和y,它们的和就在范围-2w<=x+y<=2w-2之内,要想准确表示,可能需要w+1位。像以前一样,我们通过将表示截断到w位,来避免数据大小的不断扩展。定义为整数和x+y被截断为w位的结果,并将这个结果看作是补码数。

原理:补码加法

对满足-2w-1<=x,y<=2w-1-1的整数x和y,有:

图2-19说明了这个原理,其中,左边的和x+y的取值范围为-2w<=x+y<=2w-2,右边显示的是该和数截断为w位补码的结果。当x+y超过TMaxw时(情况4),我们说发生了正溢出。在这种情况下,截断的结果是从和数中减去2w。当x+y小于TMinw时(情况1),我们说发生了负溢出,截断的结果是把和数加上2w

图2-19   整数和补码加法之间的关系。当x+y小于-2w-1时,产生负溢出。当它大于2w-1时,产生正溢出

推导:补码加法

既然补码加法和无符号数加法有相同的位级表示,我们就可以按如何步骤表示运算:将其参数转换为无符号数,执行无符号数加法,再将结果转换为补码:  

根据整数表示中的等式(2.6),我们可以把T2Uw(x)写成xw-12w+x,把T2Uw(y)写成yw-12w+y。使用属性,即是模2w的加法,以及模数加法的属性,我们就能得到:

消除了xw-12w和yw-12w这两项,因为它们模2w等于0。

为了更好地理解这个数量,定义了z为整数和,而。数值等于。我们分成4种情况分析,如图2-19所示。

  1.  -2w<=z<-2w-1。然后,我们会有z'=z+2w。这就得出0<=z'<-2w-1+2w=2w-1。检查整数表示中的等式(2.7),我们看到z'在满足z''=z'的范围之内。这种情况称为负溢出。我们将两个负数x和y相加,得到一个非负的结果z''=x+y+2w
  2. -2w-1<=z<0。那么我们又将有z'=z+2w,得到-2w-1+2w=2w-1<=z'<2w。检查等式(2.7),我们看到z'在满足z''=z'-2w的范围之内,因此,z''=z'-2w=z+2w-2w=z。也就是说,我们的补码和z''等于整数和x+y。
  3. 0<=z<2w-1。那么,我们将有z'=z,得到0<=z'<2w-1,因此z''=z'=z。补码和z''又等于整数和x+y。
  4. 2w-1<=z<2w。我们又将有z'=z,得到2w-1<=z'<2w。但是在这个范围内,我们有z''=z'-2w,得到z''=x+y-2w。这种情况称为正溢出。我们将正数x和y相加,得到一个负数结果z''=x+y-2w

图2-20展示了一些4位补码加法的示例作为说明。每个示例的情况都被标号为对应于等式(2.13)的推导过程中的情况。注意24=16,因此负溢出得到的结果比整数和大16,而正溢出比之和结果小16。我们包括了运算数和结果的位级表示。可以观察到,能够通过对运算数执行二进制加法并将结果截断到4位,从而得到结果。

图2-20   补码加法示例。通过执行二进制运算数的加法并将结果截断到4位,可以获得4位补码和的位级表示

图2-21阐述了字长w=4的补码加法。运算数的范围为-8~7之间,当x+y<-8时,补码加法就会溢出,导致和增加了16。当-8<=x+y<8时,加法就产生x+y。当x+y>=8,加法就会正溢出,使得和减少了16。这三种情况中的每一种都形成了图中的一个斜面。

图2-21   补码加法(字长为4位的情况下,当x+y<-8时,产生负溢出;x+y>=8时,产生正溢出)

等式(2.13)也让我们认出哪些情况下会发生溢出:

原理:检测补码加法中的溢出

对满足TMinw<=x,y<=TMaxw的x和y,令。当且仅当x>0,y>0,但s<=0时,计算s发生了正溢出。当且仅当x<0,y<0,但s>=0时,计算s发生了负溢出。

图2-20显示了当w=4时,这个原理的例子。第一个条目是负溢出的情况,两个负数相加得一个正数。最后一个条目是正溢出的情况,两个正数相加得一个负数。

推导:检测补码加法中的溢出

如果x>0,y>0,而s<=0,那么显然发生正溢出。所以正溢出的条件为x>0,y>0且x+y<=0。同样的讨论也适用于负溢出。

补码的非

可以看到范围在TMinw<=x<=TMaxw中的每个数字x都有下的加法逆元,我们将表示如下。

原理:补码的非

对于满足TMinw<=x<=TMaxw的x,其补码的非由下式给出

也就是说,对w位的补码加法来说,TMinw是自己加法的逆,而UI其他任何数值x都有-x作为其加法的逆。

推导:补码的非

观察发现TMinw+TMinw=-2w-1+(-2w-1)=-2w。这将导致溢出,因此。对满足x>TMinw的x,数值-x可以表示为一个w位的补码,它们的和-x+x=0。

无符号乘法

范围在0<=x,y<=2w-1内的整数x和y可以被表示为w位的无符号数,但是它们的乘积的取值范围为0到(2w-1)2=22w-2w+1+1。这可能需要2w位来表示。不过,C语言中的无符号乘法被定义为产生w位的值,就是2w位的整数乘积的低w位表示的值,我们将这个值表示为

将一个无符号数截断为w位等价于计算该值的模2w,得到:

原理:无符号数乘法

对于满足0<=x,y<=UMaxw的x和y有:

补码乘法

范围在-2w-1<=x,y<=2w-1-1内的整数x和y可以被表示为w位的补码数字,但是它们乘积的取值范围为-2w-1*(2w-1-1)=-22w-2+2w-1到-2w-1*-2w-1=-22w-2之间。要想用补码来表示这个乘积,可能需要2w位。然而,C语言中的有符号乘法是通过将2w位的整数乘积截断为w位来实现的。我们将这个数值表示为。将一个补码数截断为w位相当于计算该值模2w,再把无符号数转换为补码,得到:

原理:补码乘法

对满足TMinw<=x,y<=TMaxw的x和y有:

我们认为对于无符号数和补码乘法来说,乘法运算的位级表示都是一样的,并用如下原理说明:

给定长度为w位的向量用补码形式的位向量表示来定义整数x和y:。用无符号形式的位向量表示来定义非负整数x'和y':。则

作为说明,图2-22给出了不同3位数字的乘法结果。对于每一对位级运算数,我们执行无符号和补码乘法,得到6位的乘积,然后再把这些乘积截断到3位。无符号的截断后的乘积总是等于x*y mod 8。虽然无符号和补码两种乘法乘积的6位表示不同,但是截断后的乘积的位级表示都相同。

图2-22   3位无符号数和补码乘法示例。虽然完整的乘积的位级表示可能会不同,但是截断后乘积的位级表示是相同的

推导:无符号和补码乘法的位级等价性

根据等式(2.6),我们有x'=x+xw-12w和y'=y+yw-12w。计算这些值的乘积模2w得到以下结果:

由于模运算符,所有带有权重2w和22w的项都丢掉了。根据等式(2.17),我们有。对等式两边应用操作T2Uw有:

将上述结果与式(2.16)和(2.18)结合起来得到。然后对这个等式两边应用U2Bw,得到:

乘以常数

在大多数机器上,整数乘法指令相当慢,需要10个或更多的时钟周期,然而其他整数运算(例如加法、减法、位级运算和移位)只需要一个时钟周期。因此,编译器使用了一项重要的优化,试着用移位和加法运算的组合来代替乘以常数因子的乘法。首先,我们会考虑以2的幂的情况,然后再概括成乘以任意常数。

原理:乘以2的幂

设x的位模式表示的无符号整数。那么,对于任何k>=0,我们都认为给出了x2k的w+k的无符号表示,这里右边增加了k个0。

因此,比如,当w=4时,11可以被表示为[1011]。k=2时将其左移得到6个位向量[101100],即可编码为无符号数11*4=44。

推导:乘以2的幂

这个属性可以通过等式2.1推导出来:

当对固定字长左移k位时,其高k位被丢弃,得到:而执行固定字长的乘法也是这种情况。因此,我们可以看出左移一个数值等价于执行一个与2的幂相乘的无符号乘法。

原理:与2的幂相乘的无符号乘法

C变量x和k有无符号数值xk,且0<=k<w,则C表达式x<<k产生数值

由于固定大小的补码算数运算的位级操作与其无符号运算等价,我们就可以对补码运算的2的幂的乘法与左移之间的关系进行类似的表述:

原理:与2的幂相乘的补码乘法

C变量x和k有补码值x和无符号数值k,且0<=k<w,则C表达式x<<k产生数值

注意,无论是无符号运算还是补码运算,乘以2的幂都可能会导致溢出。结果表明,即使溢出的时候,我们通过移位得到的结果也是一样的。回到前面的例子,我们将4位模式[1011](数值为11)左移两位得到[101100](数值为44)。将这个值截断为4位得到1100(数值为12=44 mod 16)。

由于整数乘法比移位和加法的代价要大得多,许多C语言编译器试图以移位、加法和减法组合来消除很多整数乘以常数的情况。例如,假设一个程序包含表达式x*14。利用14=23+22+2。编译器会将乘法重写为(x<<3)+(x<<2)+(x<<1),将一个乘法替换为三个移位和两个加法。无论x是无符号的还是补码,甚至当乘法会导致溢出时,两个计算都会得到一样的结果。更好的是,编译器还可以利用属性14=24-2,将乘法重写为(x<<4)-(x<<1),这时只需要两个移位和一个减法。

除以2的幂

在大多数机器上,整数除法比整数乘法更慢,需要30个或者更多的时钟周期。除以2的幂也可以用移位运算来实现,只不过我们用的是右移,而不是左移。无符号和补码数分别使用逻辑移位和算数移位来达到目的。

整数除法总是舍入到零,为了准确进行定义,我们要引入一些符号。对于任何实数a,定义为唯一的整数a',使得a'<=a<=a'+1。例如,。同样,定义为唯一的整数a',使得a'-1<a<=a'。例如,,而。对于x>=0和y>0,结果会是,而对于x<0和y>0,结果会是,也就是说,它将向下舍入一个正值,而向上舍入一个负值。

对无符号运算使用移位是非常简单的,部分原因是由于无符号数的右移一定是逻辑右移。

原理:除以2的幂的无符号数除法

C变量x和k有无符号数值xk,且0<=k<w,则C表达式x>>k产生数值

例如,图2-23给出了在12 340的16位表示上执行逻辑右移的结果,以及对它执行除以1、2、16和256的结果。从左端移入的0以斜体表示。我们还给出了用真正的运算做除法得到的结果。这些示例说明,移位总是舍入到零的结果,这一点与整数除法的规则一样。

图2-23   无符号数除以2的幂(这个例子说明了执行一个逻辑右移k位与除以2k再舍入到零有一样的效果)

推导:除以2的幂的无符号除法

x为位模式表示的无符号整数,而k的取值范围0>=k<w。设x'为w-k位表示的无符号数,而x''为低k位位表示表示的无符号数。由此,我们可以看到x=2kx'+x'',而0<=x''<2k。因此,可得

对位向量逻辑右移k位得到位向量:

这个位向量有数值x',我们看到,该值可以通过计算x>>k得到。

对于除以2的幂的补码运算来说,情况要稍微复杂些。首先,为了保证负数仍然为负,移位要执行的是算数右移。

原理:除以2的幂的补码除法,向下舍入

C变量x和k分别有补码值x和无符号数值k,且0<=k<w,当执行算数移位时,C表达式x>>k产生数值

对于x>=0,变量x的最高有效位为0。所以效果与逻辑右移是一样的。因此,对于非负数来说,算数右移k位和除以2k是一样的。作为一个负数的例子,图2-24给出了对-12 340的16位表示进行算数右移不同位数的结果。对于不需要舍入的情况(k=1),结果是x/2k。但是当需要进行舍入时,移位导致结果向下舍入。例如,右移4位将会把-771.25向下舍入为-772。我们需要调整策略来处理负数x的除法。

图2-24   进行算数右移

推导:除以2的幂的补码除法,向下舍入

x为位模式表示的补码整数,而k的取值范围0>=k<w。设w'为w-k位表示的补码数,而x''为低k位表示的无符号数。通过与对无符号数情况类似的分析,我们有x=2kx'+x'',而0<=x''<2k,得到。进一步,可以观察到,算数右移位向量k位,得到位向量

它刚好就是将从w-k位符号扩展到w位。因此,这个移位后的位向量就是的补码表示。

我们可以在移位之前“偏置”这个值,来修正这种不合适的舍入。

原理:除以2的幂的补码除法,向上舍入

C变量x和k分别有补码值x和无符号数值k,且0<=k<w,当执行算数移位时,C表达式(x+(1<<k)-1)>>K产生数值

图2-25说明在执行算数右移之前加上一个适当的偏置量是如何导致结果正确舍入的。在第三列,我们给出了-12 340加上偏量值之后的结果,低k位(那些会向右移出的位)以斜体表示。我们可以看到,低k位左边的位可能会加1,也可能不会加1。对于不需要舍入的情况(k=1),加上偏量只影响那些被移掉的位。对需要舍入的情况,加上偏量导致较高的位加1,所以结果会向零舍入。

图2-25   补码除以2的幂(右移之前加上一个偏量,结果就向零舍入了)

偏置技术利用如下属性:对于整数x和y(y>0),。例如,当x=-30和y=4,我们有x+y-1=-27,而。当x=-32和y=4时,我们有x+y-1=-29,而

推导:除以2的幂的补码除法,向上舍入

查看,假设x=qy+r,其中0<=r<y,得到(x+y-1)/y=q+(r+y-1)/y,因此。当r=0时,后面一项等于0,而当r>0时,等于1。也就是说,通过给x增加一个偏量y-1,然后再用除法向下舍入,当y整数x时,我们得到q,否则,就得到q+1。

回到y=2k的情况,C表达式X+(1<<K)-1得到数值x+2k-1。将这个值算数右移k位即产生。这个分析表明对使用算数右移的补码机器,C表达式(X<0 ? X+(1<<K)-1 : X) >>K将会计算数值x/2k

posted @ 2019-07-24 21:09  北洛  阅读(732)  评论(0编辑  收藏  举报