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

时间:2021-04-11 17:23:12

整数表示

图2-3列出了我们引入的数学术语,用于精确定义和描述计算机如何编码和操作整数:

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

图2-3   整数的数据与算数操作术语。下表w表示数据的位数

整型数据类型

C语言支持多种整型数据类型——表示有限范围整数,如图2-4和2-5所示:

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

图2-4   32位程序上C语言整型数据类型的典型取值范围

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

图2-5   64位程序上C语言整型数据类型的典型取值范围

图2-4和2-5中一个值得注意的点是取值范围不是对称的,负数的范围比整数多1。C语言标准定义了每种数据类型必须能够表示的最小的取值范围,如图2-6所示:

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

图2-6   C语言的整型数据类型保证的取值范围

图2-6的取值范围相比图2-4和2-5的典型实现一样,或者更小些。除了固定大小的数据类型是例外,我们看到他们只要求正数和负数的取值范围是对称的。此外,数据类型int可以用2个字节的数字来实现,这几乎回退到16位机器的时代。还可以看到,long的大小可以用4个字节的数字来实现,对32位程序来说这是很典型的。固定大小的数据类型保证数值的范围与图2-4给出的典型数值一致,包括负数与正数的不对称性。

无符号数的编码

假设一个整数数据类型有w位,我们可以将位向量写成信息的表示和处理之整数表示,表示整个向量,或者写成[xw-1,xw-2,……,x0],表示向量中的每一位。把信息的表示和处理之整数表示看做一个二进制表示的数,就获得了信息的表示和处理之整数表示的无符号表示。在这个编码中,每个位xi都取值为0或1,后一种取值意味着数值2i应为数字值的一部分。我们用一个函数B2Uw(Binary to Unsigned的缩写,长度为w)来表示:

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

符号“信息的表示和处理之整数表示”表示左边被定义为等于右边。函数B2Uw将一个长度为w的0、1串映射到非负整数。举个栗子,图2-6展示的是下面几种情况下B2U给出的从位向量到整数的映射:

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

在图2-7中,我们使用长度为2i的指向右侧箭头的条来表示每个位的位置i。每个位向量对应的数值就等于所有值为1的位对应的条的长度之和。

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

图2-7   w=4的无符号数示例。当二进制表示中位i为1,数值就会相应加上2i

让我们来考虑下w位所能表示的值的范围。最小值是用位向量[00……0]表示,也就是整数值0,而最大值是用位向量[11……1]表示,也就是整数值信息的表示和处理之整数表示。以4位情况为例,UMax4=B2U4([1111])=24-1=15。因此,函数B2Uw能够被定义为一个映射B2Uw:{0,1}w→{0,……,2w-1}。

无符号数的二进制表示有一个很重要的属性,每个介于0~2w-1之间的数都有唯一一个w位的值编码。例如,十进制11作为无符号数,只有一个4位的表示,即[1011]。

原理:无符号数编码的唯一性。

函数B2Uw是一个双射。

数学术语双射是指一个函数f有两面:它将数值x映射为数值y,即y=f(x),但它也可以反向操作,因为对每个y而言,都有唯一一个数值x使得f(x)=y。这可以用反函数f-1表示,即x=f-1(y)。函数B2Uw将每一个长度为w的位向量都映射为0~2w-1之间的一个唯一值;反过来,我们称其为U2Bw(即“无符号数到二进制”),在0~2w-1之间的每一个整数都可以映射为一个唯一的长度为w的位模式。

补码编码

对于许多应用,我们还希望表示负值。最常见的有符号数的计算机表示方式为补码形式。我们用函数B2Tw(Binary to Two's-complement的缩写,长度为w)来表示:

原理:补码编码的定义:

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

最高有效位xw-1也称为符号位,它的“权重”为-2w-1,是无符号表示中权重的负数。当符号位被置位1时,表示值为负。这里来看一个示例,图2-8展示的是下面几种情况下B2T给出的从位向量到整数的映射。

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

我们可以看到,图2-7和2-8中位模式一样,对等式(2.2)和等式(2.4)来说也是一样,但是当最高有效位是1时,数值是不同的,这是因为在一种情况中,最高有效位的权重是+8,而另一种情况是-8。

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

图2-8   w=4的补码示例。把位3作为符号位,因此当它为1时,对数值的影响是-23=-8。这个权重在图中用带向左箭头的条来表示。

让我们来考虑下w位补码所能表示的范围,最小值为[10……0],也就是设置这个值为负权,但是清除其他所有位,其整数值为信息的表示和处理之整数表示。而最大值是位向量[01……1],清除具有负权的位,而设置其他所有的位,其整数值为信息的表示和处理之整数表示。以长度为4为例,我们有TMin4=B2T4([1000])=-23=-8,而TMax4=B2T4([0111])=22+21+20=4+2+1=7。

我们可以看出B2Tw是一个从长度为w的位模式到TMinw和TMaxw之间数字的映射:写作B2Tw:{0,1}w→{TMinw,……,TMaxw}。同无符号表示一样,在可表示的取值范围内的每个数字都有一个唯一的w位的补码编码。这就导出了无符号数相似的补码数原理:

原理:补码编码的唯一性

函数B2Tw是一个双射。

我们定义函数T2Bw(即“补码到二进制”)作为B2Tw的反函数。也就是说,对于每个数x,满足TMinw<=x<=TMaxw,则T2Bw(x)是x的(唯一的)w位模式。

为了更好的理解补码的表示,我们考虑下面的代码:

#include <stdio.h>

typedef unsigned char *byte_pointer;

void show_bytes(byte_pointer start, size_t len) {
    size_t i;
    for (i = 0; i < len; i++)
                printf(" %.2x", start[i]); 
    printf("\n");
}

int main(int argc, char *argv[])
{
        short x  = 12345;
		short mx = -x;
		show_bytes((byte_pointer)&x, sizeof(short));
        show_bytes((byte_pointer)&mx, sizeof(short));
		return 0;
}

 

当在大端法机器上运行时,这段代码的输出为30 39和cf c7,x的十六进制表示为0x3039,而mx的十六进制表示为0xCFC7。将它们展开为二进制,我们得到x的位模式为[0011 0000  0011 1001],而mx的位模式为[1100 1111 1100 0111]。如图2-9所示,等式(2.3)对这两个位模式生成的值为12345和-12345。

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

图2-9   12345和-12345的补码表示,以及53191的无符号表示。

有符号数和无符号数之间的转换

C语言允许在各种不同的数字数据类型之间做强制类型转换。假设变量x声明为int,u声明为unsigned。表达式(unsigned)x会将x的值转换成一个无符号数值,而(int)u将u的值转换成一个有符号整数。将有符号整数强制类型转换成无符号数,或者反过来,会得到什么样的结果?从数学的角度来说,可以想象到几种不同的规则。很明显,对于在两种形式都能表示的值,我们希望能保持不变。另一方面,将负数转成无符号数可能得到0。如果转换的无符号数太大以至于超过了补码能够表示的范围,可能会得到TMax。不过,对于大多数C语言的实现来说,对于这个问题的回答都是从位级角度来看,而不是数的角度。

比如说,考虑下面的代码:

#include <stdio.h>

int main(int argc, char *argv[])
{
	short int v = -12345;
	unsigned short uv = (unsigned short) v;
	printf("v = %d, uv = %u\n", v, uv);
	return 0;
}

  

在一台采用补码的机器上,上述代码会产生如下输出:v = -12345, uv = 53191。我们看到,强制类型转换的结果保持位值不变,只是改变了解释这些位的方式。在图2-9中我们看到过,-12 345的16位补码表示与53 191的16位无符号表示完全一样。将short强制类型转换为unsigned short改变数值,但是不改变位表示。

类似地,考虑下面的代码:

#include <stdio.h>

int main(int argc, char *argv[])
{
	unsigned u = 4294967295u;
	int tu = (int) u;
	printf("u = %u, tu = %d\n", u, tu);
	return 0;
}

  

在一台采用补码的机器上,上述代码会产生如下输出:

u = 4294967295, tu = -1

  

对于32位字长来说,无符号形式的4294967295(UMax32)和补码形式的-1的位模式完全一样。将unsigned强制类型转换为int,底层的位表示保持不变。

对于大多数C语言实现,处理同样字长的有符号数和无符号数之间相互转换的一般规则是:数值可能会改变,位模式不变。我们定义U2Bw和T2Bw,它们将数值映射为无符号数和补码形式的位表示。也就是说,给出0<=x<=UMaxw范围内的一个整数x,函数U2Bw(x)会给出x的唯一的w位无符号表示。相似地,当x满足TMinw<=x<=TMaxw,函数T2Bw(x)会给出x的唯一的w位补码表示。

现在,将函数T2Uw定义为信息的表示和处理之整数表示。这个函数的输入是一个TMinw~TMaxw的数,结果得到一个0~UMaxw的值,这里两个数有相同的位模式,除了参数是无符号的,而结果是以补码表示的。类似的,对于0~UMaxw之间的值x,定义函数U2Tw信息的表示和处理之整数表示。生成一个数的无符号数表示和x的补码表示相同。

从图2-9中,我们看到T2U16(-12 345)=53 191,并且U2T16(53 191)=-12 345。也就是说,十六进制表示写作0xCFC7的16位位模式即是-12 345的补码表示,又是53 191的无符号表示。同时请注意,12 345+53 191=65 536=216。这个属性可以推广到给定位模式的两个数值(补码和无符号数)之间的关系。类似地,从图2-10我们看到T2U32(-1)=4 294 967 295,并且U2T32(4 294 967 295)=-1。也就是说,无符号表示中的UMax有着和补码表示的-1相同的位模式。我们将在这两个数之间也能看到这种关系1+UMaxw=2w

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

图2-10   重要的数字。图中给出了数值和十六进制表示

原理:补码转换为无符号数

对于满足TMinw<=x<=TMaxw的x有:

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

比如,我们看到的T2U16(-12 345)=-12 345+216=53 191,同时T2Uw(-1)=-1+2w=UMaxw。该属性可通过公式(2.1)和(2.3)推导出来。

推导:补码转换为无符号数

比较等式(2.1)和等式(2.3),我们可以发现对于位模式信息的表示和处理之整数表示,如果我们计算信息的表示和处理之整数表示信息的表示和处理之整数表示之差,从0到w-2的位的加权和将互相抵消掉,剩下一个值:信息的表示和处理之整数表示信息的表示和处理之整数表示,这就得到一个关系:信息的表示和处理之整数表示。我们因此有:

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

根据公式(2.5)的两种情况,在x的补码表示中,位xw-1决定了x是否为负。

比如说,图2-11比较了当w=4时函数B2U和B2T是如何将数值变成位模式的。对补码来说,最高有效位是符号位,我们用向左箭头的条来表示。对于无符号数来说,最高有效位是正权重,我们用向右箭头表示。从补码变为无符号数,最高有效位的权重从+8变为-8。因此,补码表示的负数如果看成无符号数,值会增加24=16。因而-5变成11,而-1变成+15。

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

图2-11   比较当w=4时无符号表示和补码表示

图2-12说明了函数T2U的一般行为。如图所示,当将一个有符号数映射为它相应的无符号数时,负数就会被转成大的正数,而非负数则保持不变。

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

图2-12   从补码到无符号数的转换。函数T2U将负数转为较大的正数

原理:无符号数转换为补码

对于满足0<=u<=UMaxw的u有:

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

该原理证明如下:

推导:无符号数转换为补码

信息的表示和处理之整数表示,这个位向量也是U2Tw(u)的补码表示。公式(2.1)和(2.3)结合起来有

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

在u的无符号表示中,对公式(2.7)的两种情况来说,位uw-1决定了u是否大于TMaxw=2w-1-1。

图2-13说明了函数U2T的行为。对于小的数(<=TMaxw),从无符号到有符号的转换将保留数字的原值。对于大的数(TMaxw),数字将转为一个负值。

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

图2-13   从无符号数到补码的转换。函数U2T把大于2w-1-1的数字转换为负值

总结一下,我们考虑无符号与补码表示之间互相转换的结果。对于范围在0<=x<=TMaxw之内的值,我们得到T2Uw(x)=x和U2Tw(x)=x。也就是说,这个范围内的数字有相同的无符号和补码表示。对于这个范围以外的数值,转换需要加上或减去2w。例如,我们有T2Uw(-1)=-1+2w=UMaxw,——最靠近0的负数映射为最大的无符号数。在另一个极端,我们可以看到T2Uw(TMinw)=-2w-1+2w=2w-1=TMaxw+1——最小的负数映射为一个刚好在补码的正数范围之外的无符号数。使用图2-9为例,我们可以看到T2U16(-12 345)=65 536-12 345=53 191。

C语言的有符号数和无符号数

C语言支持所有整型数据类型的有符号和无符号运算,尽管C语言标准没有指定有符号数要采用某种表示,但几乎所有机器都使用补码。通常,大多数数字都默认是有符号的。要创建一个无符号常量,必须加上后缀字符'U'或'u',例如,12345U或0x1A2Bu。C语言允许无符号数和有符号数之间的转换,虽然C标准没有精确规定应如何进行转换,但大多数系统遵循的原则是底层的位保持不变。因此,在一台采用补码的机器上,当从无符号数转换为有符号数时,效果就是应用函数U2Tw, 而从有符号数转换为无符号数时,就是应用函数T2Uw,其中w表示数据类型的位数。

显示的强制类型转换就会导致转换发生,如下面的代码:

int tx, ty;
unsigned ux, uy;
tx = (int) ux;
uy = (unsigned) ty;

  

另外,当一种类型的表达式被赋值给另外一种类型的变量时,转换是隐式发生的,就像下面的代码:

int tx, ty;
unsigned ux, uy;

tx = ux;	/* Cast to signed */
uy = ty; 	/* Cast to unsigned */

  

当用printf输出数值时,分别用指示符%d、%u和%x以有符号十进制、无符号十进制和十六进制格式输出一个数字。注意,printf没有使用任何类型信息,所以它可以用指示符%u来输出类型为int的数值,也可以用指示符%d输出类型为signed的数值。考虑下面的代码:

#include <stdio.h>

int main(int argc, char *argv[])
{
	int x = -1;
	unsigned u = 2147483648;

	printf("x = %u = %d\n", x, x);
	printf("u = %u = %d\n", u, u);
	return 0;
}

  

运行结果:

x = 4294967295 = -1
u = 2147483648 = -2147483648

  

在这两种情况下,printf首先将变量当做一个无符号输出,然后当做一个有符号输出。以下是实际运行中的转换函数:T2U32(-1)=UMax32=232-1和U2T32(231)=231-232=-231=TMin32

由于C语言对同时包含有符号数和无符号数表达式的这种处理方式,出现了一些奇特的行为。当执行一个运算时,如果它的一个运算数是有符号的而另一个是无符号的,那么C语言会隐式地将有符号参数强制类型转换为无符号数,并假设这两个数都是非负的,来执行这个运算。图2-14展示了一些关系表达式的示例以及它们得到的求值结果,这里假设数据类型int表示为32位补码。考虑比较式-1<0U,因为第二个运算数是无符号的,第一个运算数会被隐式地转换为无符号数。因此表达式等价于4294967295U<0U,这个答案显然是错的。其他示例也可以通过相似的分析来理解。

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

图2-14   C语言的升级规则的效果。

注:非直观的情况标注了‘*’,当一个运算数是无符号的时候,另一个运算数也会被强制隐式地转换为无符号。

扩展一个数字的位表示

一个常见的运算是在不同字节长度的整数之间转换,同时又保持数值不变。要将一个无符号数转换成一个更大的数据类型,我们只要简单地在表示的开头添加0。这种运算被称为零扩展,表示原理如下:

原理:无符号数的零扩展

定义宽度为w的位向量信息的表示和处理之整数表示和宽度为w'的位向量信息的表示和处理之整数表示信息的表示和处理之整数表示,其中w'>w,则信息的表示和处理之整数表示

按照公式(2.1),该原理可以看做是直接遵循了无符号数编码的定义。

要将一个补码数字转换为一个更大的数据类型,可以执行一个符号扩展,在表示中添加更高有效位的值,表示为如下原理:

原理:补码数的符号位扩展

定义宽度为w的位向量信息的表示和处理之整数表示和宽度为w'的位向量信息的表示和处理之整数表示信息的表示和处理之整数表示,其中w'>w。则信息的表示和处理之整数表示

图2-15给出了从字长w=3到w=4的符号扩展的结果。位向量[101]=-4+1=-3。对它应用符号位扩展,得到位向量[1101],表示的值-8+4+1=-3。我们可以看到,对于w=4,最高两位的组合值是-8+4=-4,与w=3时符号位的值相同。类似地,位向量[111]和[1111]都表示-1。

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

图2-15   从w=3到w=4的符号扩展示例。对于w=4,最高两位组合权重为-8+4=-4,与w=3时的符号位权重一样

有了这个直觉,我们现在可以展示保持补码值的符号扩展。

推导:补码数值的符号扩展

令w'=w+k,我们想要证明的是

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

下面的证明是对k进行归纳。也就是说,如果我们能证明符号扩展一位保持的数值不变,那么符号扩展任意位都能保持这种属性。因此,证明任务变成了:

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

用等式(2.3)展开左边的表达式,得到:

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

我们使用关键属性是2w-2w-1=2w-1。因此,加上一个权值为-2w的位,和将一个权值为-2w-1的位转换为一个权值为-2w-1的位,这两种运算的综合效果就会保持原始的数值。

截断数字

假设我们不用额外的位来扩展一个数值,而是减少表示一个数字的位数。例如下面代码中的情况:

int x = 53191;
short sx = (short) x;	/* -12345 */
int y = sx;				/* -12345 */

  

当我们把x强制类型转换为short时,我们就将32位的int截断为16位的short int。当我们把它强制类型转换为int时,符号扩展把高位设置为1,从而生成-12345的32位补码表示。

当将一个w位的数信息的表示和处理之整数表示截断为一个k位数字时,我们会丢弃高w-k位,得到一个位向量信息的表示和处理之整数表示。截断一个数字可能会改变它的值——溢出的一种形式。对于一个无符号数,我们可以很容易地得出其数值的结果。

原理:截断无符号数

信息的表示和处理之整数表示等于向量信息的表示和处理之整数表示,而信息的表示和处理之整数表示是将其截断为k位的结果:信息的表示和处理之整数表示信息的表示和处理之整数表示。令信息的表示和处理之整数表示。则信息的表示和处理之整数表示

该原理背后的直觉就是所有被截取的位其权重形式都为2i,其中i>=k,因此,每一个权在取模操作下结构都为0,可用如下推导表示:

推导:截断无符号数

通过对等式(2.1)应用取模运算就可以看到:

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

在这段推导中,我们利用了属性:对于任何i>=k,2mod 2k=0。

补码截断也具有相似的属性,只不过要将高位转换为符号位:

原理:截断补码数值

信息的表示和处理之整数表示等于向量信息的表示和处理之整数表示,而信息的表示和处理之整数表示是将其截断为k位的结果:信息的表示和处理之整数表示信息的表示和处理之整数表示。令信息的表示和处理之整数表示。则信息的表示和处理之整数表示

在这个公式中,x mod 2k 将是0到2k-1之间的一个数。对其应用函数U2Tk产生的效果是把最高有效位xk-1的权重从2k-1转变为-2k-1。举例来看,将数值x=53 191从int转换为short。由于216=65 536>=x,我们有x mod 216=x。但是,当我们把这个数转换为16位的补码时,我们得到x'=53 191- 65 536=-12 345。

推导:截断补码数值

使用与无符号数截断相同的参数,则有

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

也就是,x mod 216能够被一个位级表示为信息的表示和处理之整数表示的无符号数表示。将其转换为补码则有信息的表示和处理之整数表示

总而言之,无符号数的截断结果是:

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

而补码数字的截断结果是:

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