写在前面:
这个实验目的在于补全所有函数,使其能够实现题意要求的功能,实验整体被分为两部分,一部分是整型,另一部分是浮点数,整型主要考察的是对位运算的相关处理和算法,浮点数部分主要考察的是对IEEE标准下浮点数表达方式的理解。强烈建议认真完成浮点数相关的函数补充(当然整型相关也要认真做),可以很好地加深对IEEE标准的理解,对将来的学习/考试有很大帮助。
一、实验项目一
1.1 项目名称
DataLab实验
1.2 实验目的
1.加强同学对位运算的理解和运用
2.加深同学对IEEE标准下浮点数表示方法的理解
1.3 实验资源
Datalab实验文件以及wsl2(搭载ubuntu20.04)
需要使用的实验文件有:bits.c、dlc以及btest(driver.pl相当于btest与dlc的组合,后面介绍),bits.c是主要的实验文件,需要补充的代码均在这个文件中,并且在文件开头会有一串英文提示(主要是告诉你函数整体的限制,例如不能使用的操作符、整型不能用条件分支等等,后续每个函数也中会有具体的限制),dlc是检测工具,用于检测你的每个函数文件是否符合对应的限制要求,如果执行完之后没有任何输出,表明所有函数文件均符合规则,btest文件则是功能检测文件,用于检测每个函数是否实现题给需求的功能,并给出每个函数的得分,完成本实验需要同时满足dlc不报错以及btest检测所有函数均满分。
1.4 整体限制条件:
1. 整型变量的范围限制在0~255,(即0~0xFF)
2. 不能使用全局变量
3. 整型部分的题目禁止使用任何控制结构,如 if、do、while、for、switch
4. 不能进行类型的强制转换
5. 不能调用任何其他函数
6.只能使用题目限定的操作符,并且只能使用有限次操作
1.5 准备工作:
首先在Linux的终端进入到datalab-handout目录,输入make all指令生成可执行文件btest、fshow和ishow(ishow和fshow,可以查看一个数在int类型和float类型下有无符号形式的值,可以不使用),也可以使用指令make btest只生成btest文件,许多同学在输入完make all指令或者使用./dlc bits.c之后,显示报错: No such file or directory,原因是没有安装交叉编译环境,输入指令进行安装:
sudo apt-get install gcc-multilib
安装完成后就可以正常执行make指令生成btest文件和使用dlc文件了,至此,准备工作已就绪。
之后每次更改bits.c中的文件后,需要输入make clean清除旧的btest文件,再重新进行make操作
二、实验任务
实验任务A:bitAnd
1.传入参数:int x,int y
2.可使用的操作符:~ |
3.最大操作符数量:8
4.分数:1
代码实现:
* 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);
}
本题利用德摩根律:x&y = ~(~x | ~y) 可以很容易得出答案
实验任务B:getByte
1.传入参数:int ,int n
2.可使用的操作符:! ~ & ^ | + << >>
3。最大操作符数量:6
分数:2
代码实现:
/*
* 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
*/
int getByte(int x, int n) {
return (x>>(n<<3) & 0xFF);
}
本题思路在于将需要获取的字节移动到最右侧,再与0xFF进行与操作,根据编号规则可知,将编号为n的字节移动到最右侧需要右移n个字节,即8n位,用左移3位得到8n。
实验任务C:logicalShift
1.传入参数:int x,int n
2.可使用的操作符:! ~ & ^ | + << >>
3.最大操作符数量:20
4.分数:3
代码实现:
/*
* logicalShift - shift x to the right by n, using a logical shift
* Can assume that 0 <= n <= 31
* Examples: logicalShift(0x87654321,4) = 0x08765432
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 20
* Rating: 3
*/
int logicalShift(int x, int n) {
return (x>>n) & ~(((1<<31)>>n)<<1);
}
由于C语言中右移操作>>是算数右移,对于负数会在左侧自动补1,所以需要将补1的位均置为0,本题需要利用算术逻辑的规则,先得到前n位为1,后32-n位为0的数,再按位取反,就可以得到前n位为0的数了,用于右移之后x前n位的置零。
实验任务D:bitCount
1.传入参数:int x
2.可使用的操作符:! ~ & ^ | + << >>
3.最大操作符数量:40
4.分数:4
代码实现:
方法一:
int bitCount(int x) {
int temp = (((((((0x1 << 8)) | 0x1) << 8) | 0x1) << 8) | 0x1);
//生成00000001 000000001 00000001 00000001的序列,以便后面的相加
int ans = 0x0;
ans = temp & x; //先计算每8个字节的第一个,将每一段的结果都加到ans里去
ans = ans + (temp & (x >> 1)); //计算2/8
ans = ans + (temp & (x >> 2)); //3/8
ans = ans + (temp & (x >> 3)); //4/8
ans = ans + (temp & (x >> 4)); //5/8
ans = ans + (temp & (x >> 5)); //6/8
ans = ans + (temp & (x >> 6)); //7/8
ans = ans + (temp & (x >> 7)); //8/8
ans = ans + (ans >> 16); //先将第1,3和2,4分别相加
ans = ans + (ans >> 8); //再将上一步得到的结果加到最后8位中
return ans & 0xff; //只取最后8位
}
方法二:
/*
* 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 a1=0x55; //01010101
int a2=0x33; //00110011
int a3=0x0F; //00001111
int a4=0xFF; //00000000 11111111
int a5=a4 | (a4<<8);
a1=a1 | (a1<<8);
a1=a1 | (a1<<16);
a2=a2 | (a2<<8);
a2=a2 | (a2<<16);
a3=a3 | (a3<<8);
a3=a3 | (a3<<16);
a4=a4 | (a4<<16); //生成相应的掩码
x=(x & a1)+((x>>1) & a1);
x=(x & a2)+((x>>2) & a2);
x=(x & a3)+((x>>4) & a3);
x=(x & a4)+((x>>8) & a4);
x=(x & a5)+((x>>16) & a5);
return x; //设置掩码将不参与计算的位置零。
}
由于方法一打了注释,这里重点介绍方法二:
首先,对于一个二进制数中1的个数,等价于把这个二进制数的每一位加起来的结果,为了提高效率本题采用二分法,分组进行求和,从右至左编号为1-32,首先一个数一组,将偶数组的数加到奇数组,再两个数一组、四个数一组、……、直到十六个数一组,重复第一次求和的操作,得到的结果即为该二进制数中1的个数。(为了实现分组求和,需要引入掩码来对原二进制数进行处理,即图中的a1~a4)
实验任务E:bang
1.传入参数:int x
2.可使用的操作符:~ & ^ | + << >>
3.最大操作符数量:12
4.分数:4
代码实现:
/*
* bang - Compute !x without using !
* Examples: bang(3) = 0, bang(0) = 1
* Legal ops: ~ & ^ | + << >>
* Max ops: 12
* Rating: 4
*/
int bang(int x) {
int x1=(~x+1);
return ((x | x1)>>31)+1;//利用符号位来分析判断,注意不能使用异或
}
对于这道题目,要实现取反操作:输入0返回值为1,输入其他值均输0,这个时候就要考虑到0和其他所有数字的一个共同区别,即0的相反数是本身,符号位不发生变化,所以我们可以利用这一点,将x与x的相反数进行异或,然后把符号位算数右移至最右侧,0对应的是全0,而其他数是全1,再加上1就完成了取反操作。
实验任务F:tmin
传入参数:void
可使用的操作符:! ~ & ^ | + << >>
最大操作符数量:4
分数:1
代码实现:
/*
* tmin - return minimum two's complement integer
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 4
* Rating: 1
*/
int tmin(void) {
return (1<<31);
}
本题较为容易,需要知道最小的int型数字为0x80000000,将其输出即可,需要注意的是本题限定使用的常数范围在0xFF之内,所以用左移操作来得到结果。
实验任务G:fitsBits
传入参数:int x,int n
可使用的操作符:! ~ & ^ | + << >>
最大操作符数量:15
分数:2
代码实现:
/*
* fitsBits - return 1 if x can be represented as an
* n-bit, two's complement integer.
* 1 <= n <= 32
* Examples: fitsBits(5,3) = 0, fitsBits(-4,3) = 1
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 15
* Rating: 2
*/
int fitsBits(int x, int n) {
int bias=33+(~n);
return !(x^((x<<bias)>>bias));
}
本题考虑对于正数体现为前33-n位为0,负数体现为前33-n位均为1,也就是说前32-n位是没有实际作用的填充位,那么左移32-n位,再右移32-n位得到的数应该和原数相等,就可以得到答案,相等用异或取反判断。
注:在完成本题时,会出现代码功能正常,但是btest跑出来得分为0的情况,这里需要从btest的工作原理来解释: