CSAPP第一章实验详解
注:这些实验都是自己写的,所以有很大的优化空间,有更好答案的小伙伴们欢迎在底下留言或者私信我哦。
bitXor(x,y)
只使用两种位运算实现异或操作。这个算是一个比较简单的问题了,难度系数1。
解题思路:异或运算是相同的数运算结果为0,否则为1。本题采取的思路是计算两个数,第一个数x,y该位同时为0该位为0,其他位为1。第二个数x,y该位同时为1时该位为0,其他位为1。再将两个数进行&运算,即可得到最终运算结果。
int bitXor(int x, int y) {
return (~(x & y))&(~((~x)&(~y)));
}
tmin()
使用位运算获取对2补码的最小 int 值。这个题目也是比较简单
解题思路:这题没什么好说的,最小int值是0x80000000,1左移31位即可。
int tmin(void) {
return 1<<(31);
}
isTmax(x)
通过位运算计算是否是补码最大值
解题思路:补码最大值是0x7FFFFFFF。观察这个数的形式,我们发现加1后,这个数就变成了补码最小值,而这个数与其位数全部取反是一样的。所以我们可以通过验证这个数加1的值与其全部位取反之后值的相等关系来判断是否是最大值。而异或就代表了相等关系。但我们要排除一个特殊情况,即0xFFFFFFFF加1后也与其位数全部取反是一样的。排除了这个特殊情况之后,我们就能得到正确答案了。
int isTmax(int x) {
return (!((x + 0x01)^(~x) )&!!(x+1))&0x01;
}
allOddBits(x)
判断所有奇数位是否都为1,这里的奇数指的是位的阶级是2的几次幂。重在思考转换规律,如何转换为对应的布尔值
*解题思路:*判断所有奇数位是否为1,可以采取迭代的方法。具体迭代格式如代码所示,其基本思想是所有奇数位的数&运算,最后将该数存储在第二位的数中。判断此时第二位数是否为1,只有当所有奇数位为1时,第二位数才为1,否则为0,以此作出判断即可。
int allOddBits(int x) {
x &= (x>>16);
x &= (x >> 8);
x &= (x >> 4);
x &= (x>>2);
return !((x & 0x2)^0x2);
}
negate
不使用 - 操作符,求 -x 值。这个题目是常识。
不懂的小伙伴可以看一下补码和反码的关系。
int negate(int x) {
return ~x + 1;
}
isAsciiDigit
计算输入值是否是数字 0-9 的 ASCII 值。
解题思路: 本题就是判断x在0x30 和0x39范围之间。首先利用右移判断第二个字节是否为3,这是基本条件。然后判断低字节,采取的判断方式为判断最高位是否为1,若为1则不属于(此时需要排除掉8和9两个情况)。即可解出本题。
int isAsciiDigit(int x) {
int y = x&0xF;
return (!((x >> 4) ^ 0x3)) &(!(y ^ 0x9) | !(y >> 3) | !(y^0x8));
//&(!(x^(x&0xFF)));
}
conditional
使用位级运算实现C语言中的 x?y:z三目运算符。又是位级运算的一个使用技巧。
解题思路:三目运算符的效果大家应该都很清楚。这道题可以考虑将x转换为全0或者全1,再利用&,|运算决定输出的是y还是z。
int conditional(int x, int y, int z) {
int x2 = ~(!!x) + 1;
return (x2 & y) | (~x2 & z);
}
isLessOrEqual
使用位级运算符实现<=
解题思路:这道题是判断x和y的大小。基本思想是用减法判断,首先计算出x-y的结果(利用取反+1)。然后把x,y以及减法结果右移31位来获取其正负性。然后分情况讨论,第一种是x-y<0,且x,y符号相同(防止产生溢出)。第二种是x,y正负性相异,此时应该是x<0,y>0。第三种是,当y位TMin时,x一定<=y,但此时前两种情况无法判断,需单独讨论。第四种是x和y相等,用异或便可以解决。
int isLessOrEqual(int x, int y) {
int xy = x + (~y + 1);
int sxy = xy >> 31;
int sx = (x >> 31) ;
int sy =(y >> 31) ;
return (!!sxy & !(sx ^ sy)) | ((sx ^ 0x0) & !(sy ^0x0)) | !(x^y) & !(!(y << 1) & (y ^0x0));
}
logicalNeg(x)
使用位级运算求逻辑非 !
解题思路: 本题的思路和前面判断全部奇数位为1有点像。利用迭代将1全部都集中在最高位,|运算代表只要有一个为1,就为1。再右移31位,因为是算术右移,再加1即可。
int logicalNeg(int x) {
x |= (x << 16);
x |= x << 8;
x |= x << 4;
x |= x << 2;
x |= x << 1;
return ((x >> 31) + 1);
}
补充:
这题可能写复杂了,我们只要保证x为0时,最高位为0。x不为0时,最高位为1即可,所以可以用x与其相反数取或运算,因为正负性相异,所以最高位一定为1。而0不管负数与否,最高位都是0。要注意的一点是,当x为TMin时,其最高位也不变,但是它的最高位就是1,所以没有影响。
return ((x|(~x+1))>>31)+1;
howManyBits
求值:“一个数用补码表示最少需要几位?”
解题思路:我们如果用最少的位数表示负数,那么它符号位之后一定是0。我们可以采取对负数取反的方式,将其和正数统一起来,用相同的方式计算最少表示位数。(符号位变成0,有效位数最高位变成1)。然后利用迭代的方法进行判断,首先判断高16位是否有1,判断方法是将x右移16位,若此时x为0,则说明高16位无数。否则说明x高16位有数,此时x肯定包含低16位,所以表示位数加上16,再将x右移动16位,判断高16位的情况。以此类推,直到判断最高两位时,右移1位,最高位是1,则加1,否则加0,但原本应该加2,加1的,所以少加了一位。再加上符号位,最后应该再加上2。然后对特殊情况-1,0单独处理即可得到最后答案。
int howManyBits(int x) {
int shift1,shift2,shift4,shift8,shift16;
int sum;
int t = ((!(x) << 31)>>31);
int t2 = (!(~x)<<31)>>31;
int op = x ^(x >> 31);
shift16 = (!!(op>>16)) << 4;
op = op >>shift16;
shift8 = (!!(op >> 8)) << 3;
op = op >> shift8;
shift4 = (!!(op>>4)) <<2;
op = op >> shift4;
shift2 = (!!(op >> 2)) <<1 ;
op >>= shift2;
shift1 = (!!(op>>1));
op = op >> shift1;
sum = 2 + shift16 + shift8 + shift4 + shift2 +shift1;
return (t2 & 1) | ((~t2) & (t & 1)) | ((~t)&(~t2)&sum);
}
floatScale2
求2乘一个浮点数
*解题思路:*这道题考察的是浮点数怎么表示的。浮点数成乘2,首先想到的是左移一位。但是浮点数在exp是否等于0时有两种表示方式,所以需要分别考虑。当exp=0是,对于小数部分最高位不为1的时候,简单左移一位即可,但最高位为1时,左移一位会超出整数的表示范围,但我们发现这个数可以用exp+1来表示,于是就用exp+=f>>22来表示指数。当exp不等于0时,对exp不等于0的情况,我如果exp<255,那么对exp简单加一。若exp=255,直接返回原本的数字即可。
unsigned floatScale2(unsigned uf) {
int sx = (uf >>31);
int exp = (uf >> 23) & 0xFF;
int f = uf &(0x7FFFFF);
int neg = !(exp ^ 0xff);
//int neg1 = !(exp ^0xfe);
if((!neg) ){
if (!(exp ^ 0)){
exp += f >> 22;
f <<= 1;
f = f & (0x7FFFFF);
}
else{
exp += 1;
}
}
return (sx << 31) | (exp << 23) |f ;
}
floatFloat2Int
将浮点数转换为整数
*解题思路:*首先提取浮点数的符号位,指数部分和小数部分。浮点数转整数都是直接把尾数舍掉(浮点数有23位有效数字,整数有32位有效数字)。考虑四种情况,exp<127时,此时指数<=0,整数部分为0,舍掉小数部分,直接得到0。当exp>=127 && exp <=150时,指数大于等于0,小于等于23,说明浮点数的仍然有有一部分是小数,舍掉小数部分。当exp>=151&&exp<=157时,指数在24~30之间,此时浮点数全部为整数,且没有超出int表示的范围(注意此时浮点数的小数部分前要多一个1)。指数再大就超出范围,返回0x80000000即可。
int floatFloat2Int(unsigned uf) {
unsigned sx = uf >> 31;
int exp = (uf >> 23) & 0xFF;
int f = uf&(0x7FFFFF);
if(exp < 127){
return 0;
}
else if(exp >=127 && exp <=150){
f |= 0x800000;
f = f >> (23 - (exp - 127));
}
else if(exp >= 151 && exp <=157){
f |= 0x800000;
f <<= (exp - 127);
}
else{
return 0x80000000;
}
if (sx){
return -f;
}
else{
return f;
}
}
floatPower2(int x)
求2^x
*解题思路:*分情况讨论。当exp=0时,它可以表示2的负数次方,这个数是用小数位来表示。float的小数位一共有23位,所以一共可以表示23个数字,范围(次方)是(-23-126)~(-1-126),它小数1所在的位置可以用23 - (-x - 126)计算出来。当exp!=0时,它必须让f的小数位全部置为0,表示的范围(次方)是(-126,127),exp=x+127。大于127时,超出了范围,直接返回固定值即可。
unsigned floatPower2(int x) {
int exp = 0;
int f = 0;
if(x < -149){
return 0;
}
else if(x >= -149 && x <-126){
f = 1 << (23 - (-x - 126));
exp = 0;
}
else if (x > 127){
return 0x7F800000;
}
else if(x >= -126){
exp = x + 127;
f = 0;
}
return (0<<31) | (exp <<23) |f;
}