实验内容及操作步骤: 1、补充完成bits.c中各个函数的内容,并在dlc中验证。 1-1、bitAnd:这个函数是将两个数x,y进行与运算。根据以前学的离散数学知识,利用德摩根律的,~(x&y)=(~x)|(~y),可得结果。
int bitAnd(int x, int y) {
x=(~x)|(~y);
return ~x;
}
1-2、getByte:这个函数是要得到X的第n个字节.则我们需要把留的字节移到最右端,再与0xff相与。0xff代表八位里面后两位是f,最后一句话是为了保证在范围内。
int getByte(int x, int n) {
x=x>>(n<<3);
return x&0xff;
}
1-3、logicalShift:实现逻辑右移。我们需要先求出一个左端有n个0,右端是32-n个1的常数,得到这个常数之后就可以和x进行算术右移后的结果相与,这样左端的n位就=0,满足逻辑右移。
int logicalShift(int x, int n) {
int y=((~0)<<(32+~n)<<1);
return (x>>n)&(~y);
}
1-4、bitCount:求出32位整数中1的个数。 本题使用了分治思想,对于一个二进制数,要对其中为1的位做计数, 对于1位二进制数来说,1的个数无非就是其本身所表示的1或0。利用这个特性,我们可以先将一个二进制数每一位独立分开为相间隔的两部分, 其每位表示的就是自身的二进制个数,再将两串二进制数对其相加,所得到的每两位分隔的二进制数就是表达这个位置的位为1的个数。
- ☆本题采用了掩码:0x55555555 \ 0x33333333 \ 0x0f0f0f0f \ 0x0000ffff
int bitCount(int x) {
int tmp, l1, l2, l4, l8, l16; //tmp is used to save ops
tmp = (0x55 << 8) + 0x55;
l1 = (tmp << 16) + tmp; //0x55555555
tmp = (0x33 << 8) + 0x33;
l2 = (tmp << 16) + tmp; //0x33333333
tmp = (0x0f << 8) + 0x0f;
l4 = (tmp << 16) + tmp; //0x0f0f0f0f
l8 = (0xff << 16) + 0xff; //0x00ff00ff
l16 = (0xff << 8) + 0xff; //0x0000ffff
x = (x & l1) + ((x >> 1) & l1);
x = (x & l2) + ((x >> 2) & l2);
x = (x & l4) + ((x >> 4) & l4);
x = (x & l8) + ((x >> 8) & l8);
x = (x & l16) + ((x >> 16) & l16);
return x;
}
1-5、bang:不用操作符!,来实现!的功能。!x 当且仅当x为0时其为1,其余时候都为0,可以用来区分零和非零数。 非运算表现为,如果x不为 0,则结果为 1,否则结果为 0。也就是说,只要有任何一个二进制位为 1,取非得到的结果就是 0,那么我们就可以将x的所有位 “压缩”到第 0 位。如果x中含有 1,那么第0位就会是 1。 最后一个语句是将 xx 按位取反,然后只留最后一位。这样返回的就是取非的结果了。
int bang(int x)
{
x = (x >> 16) | x;
x = (x >> 8) | x;
x = (x >> 4) | x;
x = (x >> 2) | x;
x = (x >> 1) | x;
return ~x & 0x1;
}
1-6、tmin:返回最小的二进制补码。 int型能表示的最大正数时2^31-1,而2^31可以故意让其上溢出,就可以得到补码的最小值了。
int tmin(void) {
return 1<<31;
}
1-7、fitBits:x是否可以用n位二进制整数表示,能则返回1,否则返回0。 x是肯定能用32位表示的,能否用n位表示可看成符号拓展的逆过程。所以经过x<<(32-n)>>(32-n)后若与原来的x相等则表示能用n位二进制补码表示x。判断相等可利用异或,相同为0,不同为1,前面加个!得到正确映射关系。
int fitsBits(int x, int n) {
int shiftnum=32+~n+1;
int y=x<<shiftnum>>shiftnum;
return !(y^x);
}
1-8、divpwr2:计算x/(2^n)。 右移运算会向下舍入,而题目要求负数向上舍入。CSAPP 书中的解决方法是为负数添加一个 bias(偏置)值 2^k−1,这样右移的时候就可以向上舍入了。x>>31 正是为了判断正负,提取了符号位。
int divpwr2(int x, int n)
{
int bias = (x >> 31) & ((1 << n) + ~0);
return (x + bias) >> n;
}
1-9、negate:取反,求补码即可。
int negate(int x) {
return ~x+1;
}
1-10、isPositive:判断x正负。(判断符号位即可) 本来只需要将符号位移到最低位和0x1相与就得到了,但是0这个特殊情况就需要加上后面的~(!x)+1。如果x=0,后面这段结果=1,返回1,如果不等于0,!x=0,0再取反加1=0,正好返回符号位
int isPositive(int x) {
return !((x>>31)&0x1)+~(!x)+1;
}
1-11、isLessOrEqual:判断x<=y是否成立。 分成三种情况,1.x是负数,y是正数,只需要看两个的符号位;2.x是正数,y是负数,返回0;3.x,y同号,算出x-y,看结果的符号位,符号位=1并且两个不相等并且不符合情况2。
int isLessOrEqual(int x, int y) {
int sx=(x>>31)&0x1;//x符号位
int sy=(y>>31)&0x1;//y符号位
int sub=x+~y+1;//x-y
int sub1=(sub>>31)&0x1;//x-y的符号位
int yh=sx^sy;//异或
return (yh&sx)|(sub1&!!sub&!(sx^sy))|!sub;
}
1-12、ilog2:求以2为底的对数,log2x。 找到最高位的 1 是第几位减一,但直接逐位右移再判断并不是可行的方法,因为较低位也可能为 0,可能统计不到真正的最高位就以为是最高位了。所以,我们先把非最高位全都设成 1,然后再统计 1 的个数即可。 先是类似于第 5 题 “Bang” 的做法,将高位 “折” 下来。与第五题不同,这里移动的位数从低到高,因为先移 16 位的话,并不能保证高 16 位中能够正确地设 1;相反的,第 5 题使用这种顺序则是可以的。 然后是第 4 题 “bitCount” 的做法,统计有多少个 1,就可以得知最高位的 1 是第几位(从 1 开始计)。不过,由于第 1 位代表 2^0,最后还要将结果减一。 总的来说,就是先把后面的0改1,再数出所有1的个数,最后再-1。
int ilog2(int x)
{
int mask;
x = (x >> 1) | x;
x = (x >> 2) | x;
x = (x >> 4) | x;
x = (x >> 8) | x;
x = (x >> 16) | x;
mask = (0x55) | ((0x55) << 8); // 0x00005555
mask = mask | (mask << 16); // 0x55555555
x = (x & mask) + (x >> 1 & mask); // 2-bit sum
mask = (0x33) | ((0x33) << 8); // 0x00003333
mask = mask | (mask << 16); // 0x33333333
x = (x & mask) + (x >> 2 & mask); // 4-bit sum
mask = (0x0f) | (0x0f << 8); // 0x00000f0f
mask = mask | (mask << 16); // 0x0f0f0f0f
x = (x & mask) + (x >> 4 & mask); // 8-bit sum
mask = (0xff) | (0xff << 16); // 0x00ff00ff
x = (x & mask) + (x >> 8 & mask); // 16-bit sum
mask = (0xff) | (0xff << 8); // 0x0000ffff
x = (x & mask) + (x >> 16 & mask); // 32-bit sum
return x + ~0;
}
1-13、float_neg:返回参数的相反数。如果参数是INF,则返回参数本身。 参数格式虽为unsigned型,但实为浮点单精度表示,返回-uf。 根据浮点数的表示规则,先通过移位求出exp和frac,根据这两个判断是否是特殊的无穷或者无法表示的数,这种情况就直接返回参数uf,否则就通过和0x80000000异或,符号位取反,其他位不变。
unsigned float_neg(unsigned uf) {
if(((uf>>23)&0xff)==0xff&&!!(uf&0x7fffff))
{return uf;}
return uf^0x80000000;
}
1-14、float_i2f:将一个int型的数转成float单精度表示并返回。 先设置符号位,通过一个循环右移,当移后结果=1,说明数的左边只有一个1,此时就能求得阶码E。考虑特殊情况+0直接返回0,0x80000000返回0xcf000000。 接下来就求一般情况,因为传进来的参数是一个整数,所以不用考虑非规格化数,直接exp=127+E,求frac时,根据阶码E大小,如果<=23需要x左移23-E位,如果>23,就有一些尾数需要舍掉,向右移E-23,再和0x7fffff相与,得到frac。 这里就涉及到精度问题,这个整数是否能用浮点数精确表示,不能的话取谁。先是用一个res把舍掉了E-23的情况存起来,再计算丢弃的位数部分dq,比较丢弃的dq和两个浮点数间隔2(E-24)大小,如果dq>2(E-24)那就说明暂时的结果加一res+1会比res更接近真实值,如果dq=2^(E-24),就根据向偶数舍入的原则,判断frac的最低位,如果是1(奇数)就进位,也就是res+1.其他情况就直接return res。
unsigned float_i2f(int x) {
int s,E,exp,frac,e1,e2,dq,res;
s=0;//默认正数
if(x<0){
x=-x;//若是负数,求补码
s=1;//为负数再改
}
E=0;
if(x==0) return 0;
else if(x==0x80000000) return 0xcf000000;
//非特殊情况
while(1)//求阶码
{
if((x>>E)==1)//等于1时,说明左边只有一个1了
break;
E++;
}
exp=127+E;
e1=E-23;
e2=E-24;//M=x
if(E<=23)
frac=(x<<(23-E));//左端右移填0
else frac=(x>>e1);//长度>23,就得往右移走一些
frac=frac&0x7fffff;
res=(s<<31)|(exp<<23)|frac;//舍去了后面几位的结果
dq=x&~(0xfffffffff<<e1);//舍去的几位的值
if(E>23)
{
int te=1<<e2,res2=res+1;
if(dq>te)
return res2;//如果舍去的 间隔的一半
//如果=一半,根据偶舍奇入的原则,x>>e1&0x1表示的就是frac的最低位
else if((dq==te)&&(x>>e1&0x1)) return res2;
return res;
}
return res;
}
1-15、float_twice:计算浮点数*2,返回浮点数格式的结果。 移位算出exp和frac,判断是否是特殊的+0,-0,NaN,无穷,是的话就直接返回; 如果是规格化数,2就直接是阶码E+1,即exp+1; 如果是非规格化数,就是尾数(frac)位左移一位,同时判断是否会有进位,进位exp就等于1; 最后把符号位s、exp、frac或运算合并起来,返回。
unsigned float_twice(unsigned uf) {
int exp=(uf>>23)&0xff;//exp位
int frac=uf&0x7fffff;//frac位
int s=(uf>>31)&0x1;//符号位
//+0,-0,NaN,无穷几个特殊值
if(uf==0x0||uf==0x80000000||(exp==0xff))
return uf;
if(exp!=0x0)//规格化数
exp++;
else
{
exp=(frac>>22)&0x1;//非规格化数,frac最高位为1,exp才进1
frac=frac<<1;//frac*2,左移1位
}
return s<<31|exp<<23|frac;//合并结果
}
2、用dlc检查。  3、用btest测试。 528行的main函数这里不是我所填充的函数内容而是bits.c自带的,且变量设定但未被使用不影响正确性,不影响。  由此可得运行结果上是全部正确的。  由上述验证得代码正确。 4、利用ishow和fshow文件帮助验证。  实验结果及分析: 经过./dlc和./btest的检验,程序无误,所有功能都已经实现。 同时可以发现,整数部分的函数只用操作符,运行很快出结果;float型的用到if,while等语句,运行时间比int的明显长。 收获与体会: 在本次实验中,对于计算机各个运算的底层实现有了更深刻的认识。对于最大、最小值等也有了一定的概念。 主要是题目不仅要求实现功能,还需要在限定数量和种类的运算符条件下实现,就加了很多难度。因为操作符的数量的限制,为了减少遍历的次数,就不得不用到分治。但是想要进行分治,结束的条件又很难想。很多不懂的知识点都需要硬啃,但是在学习期间和舍友们的讨论也收益颇丰。 参考资料: csapp深入了解计算机系统配套datalab-handout实验_simenona的博客-优快云博客 CSAPP - Bits LAB 位运算 - 知乎 CSAPP 2e Data Lab 笔记 – cyp0633's blog |