计算机系统实验--DataLab

实验题目:Lab2–Datalab

实验目的:

修改bits.c文件,完善bits.c中的函数,使它在不违反任何编码准则的情况下通过所有btest测试。

实验环境:

虚拟机:VMware WorkStation 16.0 Pro;Linux发行版本:Ubuntu 12.04-32bits。

实验内容及操作步骤:

1. 实验要求:

1)函数体内部的基本格式如下:

int Funct(arg1, arg2, ...) {
      /* brief description of how your implementation works */
      int var1 = Expr1;
      ...
      int varM = ExprM;

      varJ = ExprJ;
      ...
      varN = ExprN;
      return ExprR;

  }

变量的声明需要放置在函数体内部的最前面,变量声明之后仅能包括对变量的一系列操作,否则会出现未定义变量的问题。
2) 各个函数的return,要求如下:
i. 数字只能使用0-0xff,不能使用像0xffffffff这样大的数字。
ii. 函数参数和局部变量。
iii. 一目运算符:~、!。
iv. 双目运算符:&、^、|、+、<<、>>。
3) bits.c中所给的15个函数都是缺失的,需要使用每个函数被允许的操作符去实现要求的功能,且使用的操作符数量需要符合上限要求。
4) 在完成整型数据部分的时候,以下操作是不被允许的:
i. 使用任何控制结构,如if,do,while,for,switch等。
ii. 定义或使用任何宏。
iii. 在此文件中定义任何其他函数。
iv. 调用任何库函数。
v. 调用任何其它的操作,如&&、||、-、or、?:。
vi. 使用任何形式的casting。
vii. 不能使用数组、结构体等。
5) 在完成浮点类型数据的时候,以下操作是不被允许的:
i. 定义或使用任何宏。
ii. 在此文件中定义任何其他函数。
iii. 调用任何库函数。
iv. 使用任何形式的casting。
v. 不能使用数组、结构或者联合。
vi. 使用任何浮点数据类型的数据、操作或者常量。
6) 在编写完bits.c文件后,需要使用dlc编译器自动检查bits.c文件是否符合编码准则。

unix> ./dlc bits.c

7)使用-e可以使dlc编译器打印每个函数使用的运算符数量,只要有合法的解决方案,就可以进行使用。

unix> ./dlc -e bits.c

8)Makefile文件使用附加代码编译bits.c文件,创建名为btest的程序。可以通过以下语句编译并运行btest程序。

unix> make btest
unix> ./btest[optional cmd line args]

9)每次更改bits.c文件后,都需要重新编译btest。当从一个平台移动到另一个平台时,我们需要丢弃旧版本的btest并生成一个新版本的btest。

unix> make clean
unix> make btest

2. 源程序清单:

1)bitAnd:

int bitAnd(int x, int y) {
	x = ~(~(x) | ~(y)); 
	return x;
}

要求:bitAnd要求我们仅利用~和|位操作符实现&位操作符的功能,最大操作符数量:8。
思路:德摩根律:A * B = !(!A + !B)。
实现:依照德摩根律可知:在C语言中,x & y = ((x) | (y)),因此,通过返回对应的和|组合表达式即可得到x & y的结果。

2)getByte:

int getByte(int x, int n) {
	x = (x >> (n << 3)) & 255;
	return x;
}

要求:getByte给定一个整数n(0 ≤ n ≤ 3),要求我们利用合法的操作符求出第n个字节对应的内容,最大操作符数量:6。最低位所在的是第0字节,最高位所在的是第3字节。
思路:我们可以将所求取的字节保留在最低位字节,然后清除高三位的字节信息,保留最低位字节的信息。
实现:每个字节是8位,因此将n左移3位(即变为8n)便是x需要右移的位数,这一步可以将求取的字节保留在最低位字节处。之后,再与0xff进行位级与运算操作,可以清除高三位,保留求取字节的信息。最后将运算结果返回即可完成要求。

3)logicalShift:

int logicalShift(int x, int n) {
	int temp = (((~(1 << 31)) >> n) << 1) | 1;
	x = x >> n;
	return x & temp;
}

要求:将有符号数x按照逻辑右移的方式移动n(0 ≤ n ≤ 31),使用的操作符为题目规定的合法运算符,最大操作数数量:20。
思路:计算机中对有符号数的右移操作执行的是算术右移,即在左边补上符号位。倘若我们将补上的符号位均设置为0,效果上等同于逻辑右移。
实现:(核心思想)将补上的符号位与0相与,将其余各位与1相与,即可得到逻辑右移的结果。利用算术左移先构造出0x80000000,再使用按位取反运算符得到0x7fffffff。先右移n位,再左移1位,最后再与0x1按位进行或运算即可得到对应的辅助参数(用于保留非填补符号位信息,将填补的符号位设置为0)。最后,将x右移n位的中间结果与辅助参数进行按位与运算即可得到逻辑右移的结果。在本程序的实现中,0x7fffffff不直接选择右移(n - 1)位是因为n的取值可能为0,-1将引发运算符功能执行出错,故程序中采用的是其它方法。

4)bitCount

int bitCount(int x) {
	int take1,take2,take4,take8,take16;
	take1 = 0x55 | (0x55 << 8);
	take1 = take1 | (take1 << 16);
	take2 = 0x33 | (0x33 << 8);
	take2 = take2 | (take2 << 16);
	take4 = 0x0f | (0x0f << 8);
	take4 = take4 | (take4 << 16);
	take8 = 0xff | (0xff << 16);
	take16 = 0xff | (0xff << 8);
	x = (x & take1) + ((x >> 1) & take1);
	x = (x & take2) + ((x >> 2) & take2);
	x = (x & take4) + ((x >> 4) & take4);
	x = (x & take8) + ((x >> 8) & take8);
	x = (x & take16) + ((x >> 16) & take16);
	return x;
}

要求:使用合法的运算符计算出有符号数x中含有多少个1,最大操作符数量:40。
思路:分治法思想。首先,计算每两位中1的个数,并用对应的两位进行存储,然后计算每四位中1的个数,用对应的四位进行存储,接着计算每八位中1的个数,用对应的八位进行存储,最后计算每十六位中1的个数,用对应的十六位进行存储。通过将高十六位存储的结果与低十六位存储的结果相加,即可得到最后的结果。
实现:take1 = 01 01 01 01……,take2 = 0011 0011 0011……,take4 = 00001111 00001111……,take8 = 0000000011111111……,take16 = 00000000000000001111111111111111。将x、x对应的右移结果分别与take1-take16进行按位与操作,同时对二者求和,就可以记录对应位数中1的个数。最后即可得到所要求取的答案。

5)bang:

int bang(int x) {
	int minusx = ~x + 1;
	int xsign,mxsign;
	xsign = ((x >> 31) & 1);
	mxsign = ((minusx >> 31) & 1);
	return ~(xsign | mxsign) + 2;
}

要求:在不使用!运算符的前提下实现!的功能,即在不使用!运算符的前提下正确求出!x的结果,最大操作符数量:12。
思路:0的符号位为0,其补码(0x00000000)的符号位依旧为0。其余有符号数的补码的符号位与原符号位均相反,因此,可以结合或运算的性质(在某一位上与1相或结果为1)实现题目的相关要求。
实现:minusx为有符号数x的相反数(从二进制表示上可理解成求该有符号数的补码)。通过将x与minusx右移31位后与0x1按位相与,即可得到x和minusx的符号位。将xsign(x的符号位)与mxsign(相反数的符号位)按位相或的结果按位取反,最低位(LSB)得到的位值即为所求结果,通过 +0x2的方式使得1-31位的位值转变为0。最后我们得到了所需要的结果。

6)tmin:

int tmin(void) {
	return (1 << 31);
}

要求:返回有符号整数的最小整数数值,最大操作符数量:4。
思路:按照有符号数的数学形式解释可知0x80000000即为有符号整数的最小整数数值。
实现:将0x1左移31位即可得到结果。

7)fitsBits:

int fitsBits(int x, int n) {
	int temp = ((x << (32 + (~n + 1))) >> (32 + (~n + 1)));
	return !(temp ^ x);
}

要求:判断能否利用n(1 ≤ n ≤ 31)位二进制数表示给定的有符号数,最大操作符数量:15。
思路:x是一个可以利用32位表示的二进制数,能否使用n位二进制数进行表示可以看作符号拓展的逆过程。因此,我们先将x左移(32 - n)位,再右移(32 - n)位得到一个中间辅助参量(该过程保持x不变)。若该辅助参量与x相等则表示x可以用n位二进制数进行表示。
实现:临时变量temp存储的是思路描述中中间辅助参量的值,基于异或(XOR)与!运算符的相关特性,x与temp的比较可以通过异或来执行。若x与temp值相等,则按位异或的结果为0x00000000,若x与temp值不等,则按位异或的结果为一个非0值。此时,再利用!运算符即可得到对应的题目要求的结果。

8)divpwr2:

int divpwr2(int x, int n) {
	int sign = (1 << 31) & x;
	int basic = (sign >> 31) & 1;
	sign = sign >> 31;
	x = (x + (basic << n) + sign) >> n;
	return x;
}

要求:将给定的有符号数x除以2n,结果要求向0舍入,最大操作符数量:15。
思路:如果x表示一个正数,则将x直接右移n位即可。若x表示一个负数,则需要加上偏置量(2n – 1)再进行右移操作。
实现:首先,使用(0x1<<31)与x按位相与得到x的符号位并存储在中间变量sign中。中间变量basic所完成的任务为:若sign为0x80000000,则将0x1存储在basic中,如果sign为0x0,则将0x0存储在basic中。接着将sign右移31位。若sign = 0x0,则右移31位后的结果依旧为0x0,若sign = 0x8000000,则右移31位后结果为0xffffffff,表示的值为-1。(basic << n) + sign完成的是偏置量的计算,如果x为正数则偏置量为0,如果x为负数则计算得到偏置量(2n – 1)。最后,将偏置后的x进行右移即可得到要求的结果。

9)negate:

int negate(int x) {
	return ~x + 1;
}

要求:给定有符号数x求出其相反数-x,最大操作符数量:5。
思路:相反数-x在二进制编码上可以理解为求有符号数x的补码,补码的计算可以通过对x“按位取反,末位加1”实现。
实现:~x + 1表示“按位取反,末位加1”,将其计算后返回即可完成任务。

10)isPositive:

int isPositive(int x) {
	int sign = (((1 << 31) & x) >> 31) & 1;
	int judge = !x;
	return !(sign | judge);
}

要求:判断有符号数x是否是一个正数,最大操作符数量:8。
思路:使用!运算符会将除0x0外的所有数返回0x0,而0x0将得到0x1。对于非负数而言,符号位均为0,对于负数而言,符号位为1。可以看到不同的分类(正数、0、负数)之间标志(!、符号位)的差异:正数(0、0),0(1、0),负数(1、1)。利用标志的差异我们就可以完成题目的相关要求。
实现:利用0x1左移31位与x按位相与可以得到符号位的信息,再将其右移31位与0x1按位相与即可把符号位信息存储在最低位中,用中间变量sign保存。用中间变量judge存储!x的结果。可以观察对标志用或运算可以将正数与非正数区分开,因此在!运算符的支持下,对sign与judge按位相或的结果进行!运算即可得到题目要求的结果。

11)isLessOrEqual:

int isLessOrEqual(int x, int y) {
	int i = 1 << 31;
	int xsign,ysign;
	int result_1,result_2,sub,subsign,judge;
	xsign = x & i;
	ysign = y & i;
	result_1 = (!!xsign) & (!ysign);
	sub = x + (~y + 1);
	subsign = (((1 << 31) & sub) >> 31) & 1;
	judge = !sub;
	result_2 = (!(xsign + (~ysign + 1))) & (subsign | judge);
	return result_1 | result_2;
}

要求:运用指定运算符判定x ≤ y是否成立,如果成立则返回1,如果不成立则返回0,最大操作符数量:24。
思路:使用y – x ≥ 0或者x – y ≤ 0判断存在溢出导致结果错误的风险,因此对相关情况进行分类讨论:当二者符号位相同时,直接相减判断正负即可。当二者符号位不相同时,则通过符号位进行判断。
实现:当符号位相同时,判断x – y的值是否为负数的思想与实现参考isPositive的相关过程,在此不过多赘述。xsign和ysign表示的是有符号数x和y的符号位。该函数中返回值为1的情况一共只有两种:(1)当二者符号位异号的时候,x的符号位为1,y的符号位为0,这就是result_1的由来。利用!运算符和与运算的相关性质,!!xsign和!ysign保证了仅仅只有xsign = 1,ysign = 0时,result_1的结果才为1。(2)当x和y符号位相等时,则根据x – y的结果进行判断即可,这就是result_2的由来。前半部分用于判断符号是否相等,后半部分用于判断x – y的值是否为负数。仅当二者同时成立,result_2的值才等于1。通过将result_1和result_2的结果相或即可得到本问题的解。

12)ilog2:

int ilog2(int x) {
	int sign,s1,s2,s3,s4,s5;
	sign = !!(x >> 16);
	s1 = sign << 4;
	x = x >> s1;
	sign = !!(x >> 8);
	s2 = sign << 3;
	x = x >> s2;
	sign = !!(x >> 4);
	s3 = sign << 2;
	x = x >> s3;
	sign = !!(x >> 2);
	s4 = sign << 1;
	x = x >> s4;
	sign = !!(x >> 1);
	s5 = sign;
	return (s1 + s2 + s3 + s4 + s5);
}

要求:求出有符号数x(x > 0)的log(x),最大操作符数量:90。
思路:这道题可以转化为求该二进制数中最高位1所在的位置。与bitCount类似,这里依旧采用分治法的思想解决该问题。
实现:代码从上到下依次解释。首先,声明中间变量sign用于判断x表示的二进制数左边的指定位数中是否含有1,如果有则sign为1,否则为0。s1的作用为记录此次判断中x即将移动的位数。如果sign为0,则不移动,如果sign为1,则代表该数表示的二进制表示中高16位含有1,因此,将x右移16位,并加以记录。s2的作用与s1类似,其用于判断x的高8位中是否含有1,如果有则将x右移8位并将移动位数加以记录。s3、s4、s5同理,它们分别用于判断x的高4位,高2位,高1位中是否含有1,如果有,则将x右移相应位数并加以记录。最后将s1-s5的结果相加即可得到最终结果。
13)float_neg:

unsigned float_neg(unsigned uf) {
	unsigned int i = 0x7f800000;
	unsigned int j = 0x007fffff;
	unsigned int k = 0x80000000;
	if((uf & i) == i)
		if((uf & j) != 0)
			return uf;
	return (uf ^ k);
}

要求:输入的无符号整数用单精度浮点数表示法按位解释,返回用无符号数32位表示的该浮点数的相反数,最大操作符数量:10。当uf表示的是NaN时,直接返回uf。
思路:根据IEEE制定的浮点数表示法规则,31位表示的是符号位,23-30位表示的是阶码,0-22位表示的是尾数。对于NaN而言,阶码为“11111111”,尾数非0,因此需要先对uf进行判断,如果不是NaN,则直接改变符号位即可。
实现:使用0x7f800000与uf按位相与获取浮点数的阶码,使用0x007fffff与uf按位相与获取浮点数的尾数。先通过两个if判断,判断uf是否表示NaN,如果不是,再利用异或的相关特性(与0异或保持不变,与1异或取反),让uf与0x80000000按位异或,改变uf的符号位,将结果返回即可得到题目所求。

14)float_i2f:

unsigned float_i2f(int x) {
	unsigned int i = 0x80000000;
	int count = 1;
	unsigned int exp = 127;
	unsigned int frac = 0x000001ff;
	unsigned int final = 0xfffffe00;
	unsigned int temp;
	unsigned int even = 0x200;
	i = x & i; //保留符号位信息 
	if(i == 0x80000000) //将负数转变为正数 
		x = -x;	
	temp = x << 1;
	if(x == 0x80000000) //特殊情况,影响循环 
		return 0xcf000000;
	if(x == 0x00000000) //特殊情况,影响循环
		return 0x00000000;
	while((temp & 0x80000000) != 0x80000000) //判断除符号位后最高位1的位置 
	{
		count = count + 1;
		temp = temp << 1;			
	}
	exp = (exp - count + 31);  
	exp  = exp << 23; //获得阶码并移动至对应地点 
	x = x << (count + 1);
	frac = frac & x;
	final = ((final & x) >> 9);
	if(frac > 0x00000100) //向偶数舍入的实现(大于) 
		final = final + 1;
	else if(frac < 0x00000100) //向偶数舍入的实现(小于) 
		final = final;
	else //向偶数舍入的实现(等于) 
	{
		even = even & x;
		if (even == 0x200)
			final = final + 1;
		else
			final = final;
	}
	return (i + exp + final); //组合各模块结果获得最终结果 
}

要求:实现int类型向float类型的转换,其中浮点数需要用无符号类型数据按照IEEE制定的规则(符号位、阶码、尾数)进行存储,最大操作符数量:30。
思路:因为传入的参数为有符号数,除特殊情况外,该数与其相反数的编码不同(相反数为其补码),而对于浮点数表示法而言,相反数之间的差异仅体现在符号位上。因此,如果传入的参数为负数,首先,我们要将其变成其相反数。之后,我们开始求取浮点数表示法中对应的模块,符号位和阶码的求取较为简单,需要注意的是尾数的求取。因为浮点数表示法中提供给尾数的只有23位,而有符号数中有效的信息位可能大于23位,因此存在精度丢失,舍入的问题。在求取尾数的时候,我们需要分类讨论,在有效位大于23位时,按照向偶数舍入的原则对尾数部分进行处理。
实现:首先使用0x80000000获取x的符号位,如果x的符号位上是1,则求其补码。这一过程通过if判断实现。之后,通过while循环寻找除符号位外最高位1的位置,由于0x0和0x80000000去除符号位后对循环跳出条件有影响(会陷入死循环),故作为特殊边界值取出,单独做返回值。找到最高位1的位置后加上偏置量即可得到阶码的数值,再通过移位操作即可转移到指定的地方。对x进行左移操作,使32位中仅保留尾数相关的信息。取前23位作为尾数基础部分,后9位作为判断舍入的依据。若后9位不处于中间值则按照“大于进位,小于保留原状态”的方式处理。若处于中间值,则看23位的最后一位,如果它是1则选择“进位+1”,如果它是0则选择“保持原状态”。最后,整合各部分结果即可得到题目要求的结果。

15)float_twice:

unsigned float_twice(unsigned uf) {
	unsigned int i = 0x80000000;
	unsigned int j = 0x7f800000;
	unsigned int k = 0x007fffff;
	unsigned int l = 0x00800000;
	unsigned int m = 0x007fffff;
	i = (i & uf); //Get the sign bit
	j = (j & uf); //Get Exp bits
	k = (k & uf); //Get Frac bits
	if(j == 0x7f800000)
		return uf; //Special
	if(j != 0x00000000)
		return(uf + 0x00800000); //Normalized
	k = (k << 1); //Denormalized
	l = (l & k);
	m = (m & k);
	j = (j + l);
	return (i | j | m);
}

要求:输入的参数为二进制无符号数,用IEEE制定的浮点数表示法进行解释,将该浮点数乘以2后的结果返回,要求返回的结果是无符号数,但是用浮点数表示法对其进行解释,最大操作数数量:30。
思路:浮点数表示法一共提供了3种表示方法,分别是:非规格化数、规格化数、特殊值。对于非规格化数而言,乘以2相当于将尾数部分整体左移一位,这里需要注意“尾数部分进位,阶码部分+1”的问题。对于规格化数而言,乘以2相当于在阶码部分+1。对于特殊值而言,题目要求直接返回输入的参数即可。通过上述对浮点数表示法的不同情况进行分类讨论,我们即可得到对应的解。
实现:首先使用0x80000000,0x7f800000,0x007fffff获取浮点数的符号位、阶码、尾数部分,再通过阶码判断这是否是特殊值,如果是特殊值则直接返回。如果是规格化数则将阶码部分+1后进行返回。以上部分可以通过if判断语句完成。如果是非规格化数,则需要对尾数部分进行左移操作,在这里需要注意进位问题。k、l、m、j用于判断进位以及存储移位后的阶码和尾数。最后将各个模块进行整合即可得到题目所求的解。

3. 预期结果:

btest程序运行无错误,各函数均能准确无误地完成指定操作,Total Point:41/41。

4. 上机调试结果:

上机调试结果与预期结果相符。

实验结果及分析:

  1. 运行./dic bits.c无报错证明程序内部的编码符合实验要求的编码规范。
  2. 运行./dic -e bits.c无报错证明补充每一个函数时使用的运算符数量符合实验要求的规范。
  3. 运行make clean清除旧版本的btest运行程序,成功清除。
  4. 运行make all成功生成新版本btest运行程序。
  5. 运行./btest,15个Function中无Errors报错,拿到了满分,证明了补充的函数的正确性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值