CSAPP_DATALAB

CSAPP_DATALAB REPORT

一、实验目的

本实验目的是加强学生对位级运算的理解及熟练使用的能力。

二、报告要求

本报告要求学生把实验中实现的所有函数逐一进行分析说明,写出实现的依据,也就是推理过程,可以是一个简单的数学证明,也可以是代码分析,根据实现中你的想法不同而异。

三、函数分析

  1. bitAnd函数

函数要求:

​函数名bit And
参数int x,y
功能实现实现与运算
要求只能用非运算和或运算,且操作数在8次内

分析:
x&y本质上就是保留x和y都为1的二进制位,所以就可以先找到x或y为0的二进制位,再取反即可

函数实现:

int bitAnd (int x, int y) {

    return ~((~x)|(~y));

}
  1. getByte函数

函数要求:

函数名getByte
参数int x,n
功能实现求x的第n个字节
要求只能用!~ & ^ | + << >>在6次操作之内完成

分析:
一个字节就是8个二进制位,对应两个十六进制位,所以对x只需要右移8n位,在取十六进制下的最低两位也就是二进制下的最低8位即可

函数实现:

int getByte(int x,int n){
	int a=0xFF;
	n<<=3;x>>=n;
	return x&a;
}
  1. logicalShift函数

函数要求:

函数名logicalShift
参数int x,n
功能实现求将x逻辑右移n位后的值
要求只能用!~ & ^ | + << >>在20次操作之内完成

分析:
逻辑右移,区别于算术右移,即不论是否为负值,右移后最高位始终补0,。普通的x>>n对于正数x将返回正确的数值,而对于负数会出现符号位右移而使高位全部为1的情况。所以要做的只是消除多余的1,也就是把负数右移n位后的高n位全部归0,同时保持低32-n位即可。

函数实现:

int logicalShift(int x,int n){
	int a=1<<31;
	a>>=n;a<<1;a=~a;
	x>>=n;
	return x&a;
}
  1. bitCount函数

函数要求:

函数名bitCount
参数int x
功能实现求x的二进制表示中1的个数
要求只能用!~ & ^ | + << >>在40次操作之内完成

分析:
首先可以想到暴力判断每一位是否为1,但每次判断及更新答案我只能做到最优3次操作,这样的总操作数不小于3x32=96,超40两倍多。但对于1的数量,又不得不判断每一位,所以要做的就是加速枚举。因为32=4*8,所以我们可以做到每次枚举4位,分8组完成枚举,即第一次枚举0,8,16,24四位,第二次是1,9,17,25,以此类推。然后去掉高位的权值加到低位,低位就是答案啦。

函数实现:

int bitCount(int x){
	int a=(1<<8)+1,ans,p;
	a+=a<<16;
	ans+=x&a;x>>=1;
	ans+=x&a;x>>=1;
	ans+=x&a;x>>=1;
	ans+=x&a;x>>=1;
	ans+=x&a;x>>=1;
	ans+=x&a;x>>=1;
	ans+=x&a;x>>=1;
	ans+=x&a;
	ans=ans+(ans>>16);
	ans=ans+(ans>>8);
	p=63;
	return ans&p;
}
  1. bang函数

函数要求:

函数名bang
参数int x
功能实现输出!x
要求只能用~ & ^ | + << >>在12次操作之内完成

分析:
输出!x,相当于判断x是否为0。所以我分负数和正数两种情况进行操作。case 1:对于负数可以取其符号位表示其非0;case 2:对于正数,我们可以发现其取值在1~231-1之间,所以我们可以将其加上-231使其拥有符号,在加上-1使0在这种情况下发生负溢出失去符号,再像负数一样判断符号位表示非0即可。

函数实现:

int bang(int x){
	int a,b,c,d;
	a=(1<<31);
	b=a&x;b>>=31;b&=1;
	c=0;c~=c;
	x=x+a+c;
	d=a&x;d>>=31;d&=1;
	d|=b;b^=1;
	return b;
}
  1. tmin函数

函数要求:

函数名tmin
参数
功能实现二进制补码表示下最小的数(其实就是求最小int类型的数)
要求只能用!~ & ^ | + << >>在4次操作之内完成

分析:
int类型范围是-231~231-1,故输出-231

函数实现:

int tmin(void){
	return (1<<31);
}
  1. fitsBits函数

函数要求:

函数名fitsBits
参数int x,int n
功能实现询问x能不能用n位二进制补码表示
要求只能用!~ & ^ | + << >>在15次操作之内完成

分析:
n位二进制补码表示x,即x的高32-n位不能存在有意义的位,当然,符号位例外。换句话说,对于正数x,x的二进制高32-n位不含1;对于负数x,高32-n位不含0。所以问题转化为比较,而且需要将正负分类比较统一起来(因为不能用if),又因为n位表示的正数第n位必须是0,左移32-n位再右移32-n位后可以使高位全0(第n位为1的正数这样操作后会因符号位变1而直接排除);同时,n位表示的负数,符合条件的x第n位一定为1,同样的操作可以保证高n-32位为1(若第n位为0,操作后符号位变0而排除)。因为移位操作下低n位不变,所以只需要亦或x和移位后得到的数值即可判断可否表示。

函数实现:

int fitsBits(int x,int n){
	int a,b;
	a=32+(~n+1);//~n+1实现n取相反数,在后面的函数中有单独实现的详解
	b=x<<a;b>>=a;x^=b;//这个部分感觉还是有点绕,不过一共2*2=4种情况分类讨论即可
	return !x;
}
  1. divpwr2函数

函数要求:

函数名divpwr2
参数int x,int n
功能实现求x/2n
要求只能用!~ & ^ | + << >>在15次操作之内完成

分析:
还是因为二进制补码带来的问题,虽然正数可以直接右移表示除2n,但负数直接右移会导致误差。不过正数可以直接右移给了负数部分的启发:负数可以先变正数进行操作,之后变回负数。所以这又变成了一个统一正负分类的问题了。得益于操作数上限是15,足够进行额外的操作进行讨论。即利用-1的补码全1,0的补码全0,使其‘与’相应的答案使其有效或无效。

函数实现:

int divpwr2(int x,int n){
	int a,b,c;
	a=0;a~=a;c=a;//小技巧,0取反是-1,避开了使用禁止的‘-’
	a+=x;a>>=31;//取符号位,利用算术右移补符号位的优势
	b=a^c;//a对应负数有效,b对应正数有效
	b&=x>>n;
	x=~x+1;x>>=n;x=~x+1;a&=x;
	return a+b;
}
  1. negate函数

函数要求:

函数名negate
参数int x
功能实现求-x
要求只能用!~ & ^ | + << >>在5次操作之内完成

分析:
二进制补码,对正数而言即原码,对负数而言即原码除符号位外取反加1。所以取相反数时首先需要修改符号位,其余低位取反加1即可,恰好对应~x+1。

函数实现:

int negate(int x){
	x=(~x)+1;
	return x;
}
  1. isPositive函数

函数要求:

函数名isPositive
参数int x
功能实现判断x是否为正
要求只能用!~ & ^ | + << >>在8次操作之内完成

分析:
判断x是否为正,可以判断x是否小于等于0,这里还是分类讨论一下。对于负数,可以取符号位判断;对于0,可以取逻辑非。两者取或,即满足其中任意一项者即小于等于0,再取非得x是否为正数。

函数实现:

int isPositive(int x){
	int a=1<<31;
	a&=x;a>>=31;a&=1;
	x=!x;a|=x;a=!=a;
	return a;
}
  1. isLessOrEqual函数

函数要求:

函数名isLessOrEqual
参数int x,y
功能实现判断x<=y正确与否
要求只能用!~ & ^ | + << >>在24次操作之内完成

分析:
这题我还是分了两种情况,使用-1/0(有效/无效)来实现的。case 1:x,y异号,这种情况直接可以根据谁有负号谁小来判断;case 2:x,y同号,这样可以使y=y-x(因为同号所以不存在溢出的问题),再判断y有无负号来判断大小。

函数实现:

int isLessOrEqual(int x,int y){
	int a,b,c,d;
	a=1<<31;b=a;c=a;
	b&=x;b=!b;
	c&=y;c=!c;
	d=b^c;c&=(!b);
	y+=~x+1;
	a&=y;
	a=!a;a&=!d;
	d&=c;
	return a+d;
}
  1. ilog2函数

函数要求:

函数名ilog2
参数int x
功能实现x对2取对数并下取整
要求只能用!~ & ^ | + << >>在90次操作之内完成

分析:
首先考虑暴力,枚举每一位有无来进行答案更新,但这样操作数不少于3x32=96次>90次。所以还是加速枚举,考虑到x的最大值为231-1,即答案小于31。于是可以利用倍增的思想31=16+8+4+2+1,即从大到小枚举2的相应次方,从而加速枚举。

函数实现:

int ilog2(int x){
	int ans=0,p,o,t;
	o=~0;
	p=x>>16;p=!p;p+=o;t=p&16;ans+=t;x>>=t;
	p=x>>8;p=!p;p+=o;t=p&8;ans+=t;x>>=t;
	p=x>>4;p=!p;p+=o;t=p&4;ans+=t;x>>=t;
	p=x>>2;p=!p;p+=o;t=p&2;ans+=t;x>>=t;
	p=x>>1;p=!p;p+=o;t=p&1;ans+=t;
	return ans;
}

四、实验总结

总结分析:

  1. 关键点:
  • 不能用减号,有的时候需要减1,则需要加-1,而-1是0按位取反的结果。
  • -1的性质非常特殊,因为它的补码表示为全1,而加1得到的0补码为全0,可以用来实现if的操作
  • 不能用if不意味着不能分类讨论,不能用循环控制不意味着不能使用重复操作
  1. bitCount函数中按位枚举的优化后的ans处理最初是&(28-1)导致高位的大权值1被计算在内,后来发现结果不超过32,选择了63作为与的参数(因为63=32+16+8+4+2+1)。
  2. bang函数的难点就在于对于正数的处理,因为正数没有统一的与0不同的位置,所以要考虑使其转化为负数,同时0不被转变为负数,考虑相对位置的不变性,使0在-231之后减一溢出是关键。
  3. fitsBits函数分四类讨论看似很复杂,但有两种情况会被直接排除,导致程序实现并不复杂。
  4. divpwr2函数还是要分类讨论,正数的处理通过负数取反来延伸很重要。
  5. isLessOrEqual函数要注意不能直接y-x,这样对大正数减大负数或大负数减大正数的情况会导致溢出,从而得不到正确答案。
  6. ilog2函数暴力枚举倍增优化是acm及oi的常见知识点,可以将O(N)算法复杂度降至O(lgn)级别,与本题的应用场景十分契合。

实验建议

  1. 好像没有给关于浮点数类型的实验,感觉最好还是布置一下。
  2. 感觉有些分值不高的题目如fitsBits及divpwr2函数的实现难度大于isLessOrEqual等分数较高的题目。但毕竟是个人感觉,可能不准,可以调查一下同学的感受然后更改题目分值及顺序。
  3. 可能是个人做题习惯,很多函数中出现了~0得到-1的操作,感觉很多题目大同小异,甚至出现一个函数包含另一个函数内容的情况,大概可以精简一下题目,增加一下考察^等使用频率较少的操作的考察。
  4. 前面相当多的题目还是在考察位运算二进制数,比较表层,可以适当考察一下实际应用中的如二进制状态压缩这类知识的考察,增加如最后一个ilog2一类的算法考察,也许能起到更好的效果。
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hiroxzwang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值