深入理解计算机系统系列03初步探索实验的奥秘
本文借以cmu的实验,详细写了该实验(datalab)的具体思路,借鉴了多篇博客的总结,并在一些题目中给出了多解,并且由于cmu的实验变动,虽然老师一直在讲不周山的博客,但是不周山做的题是好久以前的,又很久没有更新,我又整合了一些原理的cmu的实验题,作为补充题目,具体在注释中都已经写到。(排版可能比较丑,勿怪,还需多多练习)
话不多说,直接上手实验
/下面是题面,我删去了前面一些说明,大概意思是说一下 用法和要求(比如要求是直线型的代码,,大概不能使用循环和条件语句吧)/
/并且通过网络整理的各个版本的题目,包括现在的题目,和不周山的博客上的题目,本人只是整理了关于前面大半部分的
整数部分的题目,对浮点数的题目只上了题面,有时间会继续写/
//注意将文件放到linux环境下
在Linux下测试以上函数是否正确,指令如下:
*编译:./dlc bits.c
测试:make btest
./btest
//1
/
- bitXor - x^y using only ~ and &
- Example: bitXor(4, 5) = 1
- Legal ops: ~ &
- Max ops: 14
- Rating: 1
*/
int bitXor(int x, int y) {
return ~(~(~x&y)&~(x&~y));;//受到离散数学的一个定律的启发
}
/*
- tmin - return minimum two’s complement integer //大概意思是返回补码表示的最小数吧
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 4
- Rating: 1
*///这个数字应该是0x8000 0000
int tmin(void) {
return 1<<31;
}
//2
/*
- isTmax - returns 1 if x is the maximum, two’s complement number,and 0 otherwise
- Legal ops: ! ~ & ^ | +
- Max ops: 10
- Rating: 1
*/
//这个题一星难度无法理解了,可能是我太菜了,这题想了很久,还结合了别人的写的
//思路:最大值为0x7fff ffff,加一会变为0x10000000
//而此数加上本身后会变为0,本身加本身为0的数只有0和0x1000 0000,故而再将0xffffffff排除即可
//这里出现了一个问题,如何排除,所以我们重新提一下关于!和的区别,!是逻辑取反,大概意思是0和非0,而是按位取反
//所以排除用&给出0x7fff ffff二者都具有的情况而0xffffffff不具备后者的情况
int isTmax(int x) {
return (!(x+1+x+1))&(!!(x+1));//这里我们注意第二部分,!!并不是毫无道理的,事实上,第一个!将数字转化为逻辑值,之后第二个!就是排除了
}
//总结坑:1.!和~的区别2.排除3.两个!!4.本身加本身确实比较难想,不知道别人是怎么想到的
/*
- allOddBits - return 1 if all odd-numbered bits in word set to 1 //大致意思是基数位都设为1时返回1
- where bits are numbered from 0 (least significant) to 31 (most significant)
- Examples allOddBits(0xFFFFFFFD) = 0, allOddBits(0xAAAAAAAA) = 1
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 12
- Rating: 2
*/
//这里注意一下,从例子可以看出,这里的奇数和偶数是从0算起的
//然后此时发现这个时候数字的位置是确定的,之后我们按位取反,得到的数也是固定的,思路就此出现,x与一个固定的数&之后为零,再!
//把那个固定的数求出来
int allOddBits(int x) {
unsigned inta=85;
unsigned intb=a+(a<<8)+(a<<16)+(a<<24);
return !(b&x);
}
/*
- negate - return -x
- Example: negate(1) = -1.
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 5
- Rating: 2
*/
//这道题反而两颗星,难以理解,课上的重点知识点
int negate(int x) {
return ~x+1;
}
//3
/*
- isAsciiDigit - return 1 if 0x30 <= x <= 0x39 (ASCII codes for characters ‘0’ to ‘9’)
- Example: isAsciiDigit(0x35) = 1.
- isAsciiDigit(0x3a) = 0.
- isAsciiDigit(0x05) = 0.
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 15
- Rating: 3
*/
//这道题的初始想法是从反面来求,后来写出来发现直接析取出几位就行了
int isAsciiDigit(int x) {
return (!((x>>3)&0x7)^(0x7))&(!(x>>6))//这题答案应该是不固定的,我没有使用+号
}//这里我们得到了判断一个数是否等于一个常数的方法,看^之后是否为0
//下附加从一个博客中看到的方法 return(!((x+~48+1)>>31))&!!((x+~58+1)>>31);具体思路是将x与临界点作差,然后右移之后观察符号位
/*
- conditional - same as x ? y : z
- Example: conditional(2,4,5) = 4
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 16
- Rating: 3
*/
//条件运算符的实现方法,首先想法是通过判断x是否非零,然后去向两个通路,两个通路如何表示那,想了想,觉得可以通过y和z都进行某种操作
//的或来表示,注意这里y与z的操作应该是相反的
int conditional(int x, int y, int z) {
return ((!x+~1+1)&y)|((~!x+1)&z)
}//这里结合一下网上一个博客思路,大体的部分已经想出来了,主要是需要两个部分,分别掌握在x为0和非0的时候的值,这个做法是先将
//x转化为!x的这个时候我们就有两种情况了,0和1,我们析取的时候需要-1,化为0的时候需要零,但是最后一步好像可以不用这么麻烦
//都减去1就可以了,即return ((!x+~1+1)&y)|((!!x+~1+1)&z)
/*
- isLessOrEqual - if x <= y then return 1, else return 0
- Example: isLessOrEqual(4,5) = 1.
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 24
- Rating: 3
/
//一开始想从反面看,直接看x-y的符号位,好像就可以
//但是后来想了想,好像会有溢出的情况哎
//于是看了下网上的思路,果然要复杂很多
/
1.在x与y同号的情况下转换为p=y-x>=0,然后p符号位(p>>31)&1为0则返回1,符号位1则返回0;
2.异号时,只要x>=0,就要返回0,否则返回1,由(x>>31)&1能达到该效果;
3.c=a+b可作为x,y同号异号的判断。
*/
//而后开始具体实现上的东西,其实是差不多的,但是多了一个溢出部分的处理
int isLessOrEqual(int x, int y) {
int a=x>>31;
int b=y>>31;
int c=a+b;//有三种情况,0,1,2但是其实是两种情况,1和非1,1代表异号,非1代表同号
int p=y+(~x+1);//p=y-x
int q=!((p>>31)&1);//q=p符号位的非
int r=(c&(a&1))|((~c)&q);//这里有一个点,如何判断同号和异号怎么做成分枝
//注意,因为这里是只判断一个位的,所以c=0和2是没有区别的
return r;
}
//总结:这道题确实比较难了,有一些过程比较难想,主要考察了关于溢出的一些理解
//4
/*
- logicalNeg - implement the ! operator, using all of
- the legal operators except !
- Examples: logicalNeg(3) = 0, logicalNeg(0) = 1
- Legal ops: ~ & ^ | + << >>
- Max ops: 12
- Rating: 4
*/
//此时我们有两种情况,0和非0,如何判定为0那
//有一种想法,应该会用到x+~x,但是还不知道怎么用
//之后还是参考了别人的代码,构造了y=~x+1,这个是真的不好想,十分巧妙
/*A.当x为0时,两者符号位都为0;
B.当x=0x8000 0000时,两者符号位都为1;
C.否则,两者符号位为01或10;
D.根据离散数学的真值表得出(x)&(y).*/
int logicalNeg(int x) {
return((~(~x+1)&~x)>>31)&1;//注意一点,最后是必须要&1的,因为前面可能是有负数的,算数右移,会出现一堆1
//总结:这个题目真心不太好想,并且最后还有一个坑,算数右移
}
/* howManyBits - return the minimum number of bits required to represent x in two’s complement
- Examples: howManyBits(12) = 5
- howManyBits(298) = 10
- howManyBits(-5) = 4
- howManyBits(0) = 1
- howManyBits(-1) = 1
- howManyBits(0x80000000) = 32
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 90
- Rating: 4
*/
//注意这个题目的意思大概是最少位置上考虑左边数第一个为符号位
/*思路:二分法,举例x=0000 1000 1001 0000 0000 0000 0000 0000说明:
A.令y=x>>16=0000 1000 1001 0000,shift16=(!!y)<<4使用(!!y)如果y为0,则返回0,说明前16位都为0,shift16=0,否则!!y=1,shift16=16,使x=x>>shift16=10001001 0000;
B.同理,令y=x>>8=0000 1000,shift8=(!!y)<<3使用(!!y)如果y为0,则返回0,说明前8位都为0,shift8=0,否则!!y=1,shift8=8,使x=x>>shift8=00001000;
C.令y=x>>4=0000,shift4=(!!y)<<2使用(!!y)如果y为0,则返回0,说明前4位都为0,shift4=0,否则!!y=1,shift4=4,使x=x>>shift4=00001000;
D.令y=x>>2=0000 10,shift2=(!!y)<<1使用(!!y),如果y为0,则返回0,说明前2位(原始的x[27]x[26])都为0,shift2=0,否则!!y=1,shift2=2,使x=x>>shift2=000010;
E.令y=x>>1=0000 1,shift1=(!!y),使用(!!y)如果y为0,则返回0,说明前1位都为0,shift1=0,否则!!y=1,shift1=1,使x=x>>shift1=00001;
F.然后令sum=2+shift16+shift8+shift4+shift2+shift1即可;
G.对于负数取反码即可,对于-1和0特殊考虑。*/
//想了一段时间,放弃了,直接看了一下别人的思路
//这个题的这个解的方法,居然用到了算法方面的二分思想,而且他写的有点复杂,大概意思就是,将x不断二分
//这里他的写法有点简陋,具体是这样的,以一开始的16位右移为例,先根据他的写法判断,前16位是否为零,
//若前16位为零,那就要开始判断后16位置了,这个时候我们的x的更新其实是不变的,这时x=x>>shift16等价于x=x
//而若前16位不为零,那就要开始判断前8位了,这个时候我们的x的更新其实是要更新的x=x>>shift16,等价于x=x>>16
//我们在这个时间段储存了前面的结果,并且进行了分枝,写法十分漂亮
//而之后,我们注意一下sum的值,这个时候我们的sum会差2,这个2是凑出来的(至少我是这么认为的哈)
//还有,负数取反码的问题,因为我们并不在意我们所需要的值的结果,那么这个时候,我们在不考虑符号位的情况下,
//我们将前面的多个1都取得反码来说就行了,这个时候我们的负数取反码可以简化计算
//最后,(这个题是真的不好弄),进行了将二进制的全0和全1的情况进行考虑,他的处理方法还有点可以研究的地方
/我们给出一个t和t2(t和t2的具体含义见代码注释),当t2为1时,即此时x为-1时,会返回1,而当t2=0时候,
我们下面做一个定义,当x=0时候,t2=0并且t=-1(二进制),而当x=-1时,t2=1,并且t=1,所以用|符号连接,两边由上面的
题目的结论,我们采用将|符号两边的操作相反的操作/
int howManyBits(int x) {
int shift1,shift2,shift4,shift8,shift16;
int sum;
int t=((!x)<<31)>>31;//x为0时,t(二进制)全为1,x不为0时,全为1
int t2=((!~x)<<31)>>31;//当x为-1时,t2全为1,否则,全为0
int op=x^((x>>31));//正数不变,负数取反
//注意一下,取反码的方式
shift16=(!!(op>>16))<<4;//如果高十六位全为0,则0左移4位,不全为0,则1左移4(表示op要右移2^4位)位
op=op>>shift16;
shift8=(!!(op>>8))<<3;
op=op>>shift8;
shift4=(!!(op>>4))<<2;
op=op>>shift4;
shift2=(!!(op>>2))<<1;
op=op>>shift2;
shift1=(!!(op>>1));
op=op>>shift1;
sum=2+shift16+shift8+shift4+shift2+shift1;
return (t2&1)|((~t2)&((t&1)|((~t)&sum)));
}
/注意,该题为补充题目,属于原来cmu的实验,而且比较难,不过,印象中南京大学的那个网课好像讲了相关的内容,
而且网课的老师没有细讲,我还查阅了一些资料,最后才仔细看了看代码,才最后明白道理/
//题面的大概意思是返回二进制1的个数,因为Max ops的限制,肯定是不能一位一位地右移的
//大概思路是,将每个字节的1的个数算出来,然后通过二分的思想,转换到一个字节上,最后求出结果,
//这个思想很有趣,当时还在看网课的时候惊叹,这次补充就顺便直接补充到博客上了
/*
- bitCount - returns count of number of 1’s in word
- Examples: bitCount(5) = 2, bitCount(7) = 3
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 40
- Rating: 4
*/
int bitCount(int x) {
int result;
//int mask1=(0x55)|(0x55<<8)|(0x55<<16)|(0x55<<24);
int tmp_mask1=(0x55)|(0x55<<8);
int mask1=(tmp_mask1)|(tmp_mask1<<16);
//int mask2=(0x33)|(0x33<<8)|(0x33<<16)|(0x33<<24);
int tmp_mask2=(0x33)|(0x33<<8);
int mask2=(tmp_mask2)|(tmp_mask2<<16);
//int mask3=(0x0f)|(0x0f<<8)|(0x0f<<16)|(0x0f<<24);
int tmp_mask3=(0x0f)|(0x0f<<8);
int mask3=(tmp_mask3)|(tmp_mask3<<16);
int mask4=(0xff)|(0xff<<16);
int mask5=(0xff)|(0xff<<8);
//add every two bits
result=(x&mask1)+((x>>1)&mask1);
//add every four bits
result=(result&mask2)+((result>>2)&mask2);
//add every eight bits
//result=(result&mask3)+((result>>4)&mask3);
result=(result+(result>>4))&mask3;
//add every sixteen bits
//result=(result&mask4)+((result>>8)&mask4);
result=(result+(result>>8))&mask4;
//add every thirty two bits
//result=(result&mask5)+((result>>16)&mask5);
result=(result+(result>>16))&mask5;
return result;
}
//注意,该题也为补充题目,查离散数学真值表,由德摩尔根律可以得到结果
/*
- bitAnd - x&y using only ~ and |
- Example: bitAnd(6, 5) = 4
- Legal ops: ~ |
- Max ops: 8
- Rating: 1
*/
int bitAnd(int x, int y) {
return ~((~x)|(~y));
}
//同为补充题,题面的大概意思是返回第几个字节的数
/*
- getByte - Extract byte n from word x
- Bytes numbered from 0 (LSB) to 3 (MSB)
- Examples: getByte(0x12345678,1) = 0x56
- Legal ops: ! ~ & ^ | + << >>
- Max ops: 6
- Rating: 2
*/
//比较简单直接上思路了:最后用&255析取出一个字节的数,那么将该字节的数转换到最低位就行了
//注意一点小细节,最低位是从0开始的,n<<3代表*8,转换为字节位数的整数倍
int getByte(int x, int n) {
return (x>>(n<<3))&255;
}
下为浮点数题目,给出题面
/*
- floatScale2 - Return bit-level equivalent of expression 2*f for
- floating point argument f.
- Both the argument and result are passed as unsigned int’s, but
- they are to be interpreted as the bit-level representation of
- single-precision floating point values.
- When argument is NaN, return argument
- Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
- Max ops: 30
- Rating: 4
*/
unsigned floatScale2(unsigned uf) {
return 2;
}
/*
- floatFloat2Int - Return bit-level equivalent of expression (int) f
- for floating point argument f.
- Argument is passed as unsigned int, but
- it is to be interpreted as the bit-level representation of a
- single-precision floating point value.
- Anything out of range (including NaN and infinity) should return
- 0x80000000u.
- Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
- Max ops: 30
- Rating: 4
*/
int floatFloat2Int(unsigned uf) {
return 2;
}
/*
- floatPower2 - Return bit-level equivalent of the expression 2.0^x
- (2.0 raised to the power x) for any 32-bit integer x.
- The unsigned value that is returned should have the identical bit
- representation as the single-precision floating-point number 2.0^x.
- If the result is too small to be represented as a denorm, return
-
- If too large, return +INF.
- Legal ops: Any integer/unsigned operations incl. ||, &&. Also if, while
- Max ops: 30
- Rating: 4
*/
unsigned floatPower2(int x) {
return 2;
}