实验题目: APP Data Lab
Directions to Students :
实验目的:
Your goal is to modify your copy of bits.c so that it passes all the tests in btest without violating any of the coding guidelines.
我的目标是修改bits.c文档,完成所有函数的编写,利用dlc和btest两个工具通过所有的测试
实验环境:ubuntu12.04环境
实验内容及操作步骤:
根据要求编写函数:
(1) bitAnd
函数功能:两个数的与操作
实现要求:
bitAnd - x&y using only ~ and |
Example: bitAnd(6, 5) = 4
Legal ops: ~ |
Max ops: 8
Rating: 1
实现思路:利用摩根定理,将x and y=(x or ~y)
实现代码:
int bitAnd(int x, int y) {
return ~((~x)|(~y));
}
(2) getByte
函数功能:取出一个int数据指定的字节
实现要求:
-
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
实现思路:
首先我们将n左移3位,扩展到8的倍数,然后右移n<<3,去除字节
实现代码:
int getByte(int x, int n) {
x=x>>(n<<3);
return x&0xff;
}
(3) logicalShift
函数功能:逻辑右移
实现要求:
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
实现思路:
LogicShift实现并不复杂,但是必须注意的是,是逻辑右移不死算数右移,也就是说,如果直接进行移动会导致符号位的填充现象。因此我们要消除因为符号位填充导致的错误,这里用temp=0x7fffffff,右移n-1位,然后与x>>n相与消除填充符号位
实现代码:
int logicalShift(int x, int n) {//注意是逻辑右移,不是算数右移,不能自动补符号位
int temp;
x=x>>n;
temp=~(1<<31);//0x7fffffff
temp=((temp>>n)<<1)+1;//右移n-1位,制造0000,用于消除符号位
return x&temp;//消除最高符号位
}
(4) bitCount
函数功能:计算一个二进制数中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
实现思路:
这里的做法我一开始没有想到,但是从网上了解到了一种思路,也就是二分法,我们首先用01相间的一组数据去与x做这样的操作x=(x&list2)+((x>>1)&list2),可以算出2位中1的个数,同样的我们用0011相间的一组数去与x做:x=(x&list4)+((x>>1)&list4)可以计算4位中的1,然后用00001111做同样的操作,得到8位中的1,最有右移x,计算每8位的结果。
实现代码:
int bitCount(int x) {
int list2=(((((0x55<<8)+0x55)<<8)+0x55)<<8)+0x55;//构造0101 0101 0101 0101 0101 0101 0101 0101
int list4=(((((0x33<<8)+0x33)<<8)+0x33)<<8)+0x33;//构造0011 0011 0011 0011 0011 0011 0011 0011
int list8=(((((0x0f<<8)+0x0f)<<8)+0x0f)<<8)+0x0f;//构造0000 1111 0000 1111 0000 1111 0000 1111
x=(x&list2)+((x>>1)&list2);//计算2位中的1
x=(x&list4)+((x>>2)&list4);//计算4位中的1
x=(x&list8)+((x>>4)&list8);//计算8位中的1
//现在我们搞定每个字节中的值(8位中的1),接下来我们合并4个字节中的1
x+=(x>>8);
x+=(x>>16);
return x&0x3f;
}
(5) bang
函数功能:逻辑非操作
实现要求:
bang - Compute !x without using !
Examples: bang(3) = 0, bang(0) = 1
Legal ops: ~ & ^ | + << >>
Max ops: 12
Rating: 4
实现思路:
这个函数实现较为简单,直接利用符号扩充的特点将x取补码,然后与x相或,右移31位+1,如果是一个非0数,得到的结果是-1+1=0,否则为1.
实现代码:
int bang(int x) {
return (((~x+1)|x)>>31)+1;//利用符号扩充的特点
}
(6) tmin
函数功能:返回二进制最小整形数据
实现要求:
tmin - return minimum two’s complement integer
一的补码(one’s complement) 指的是正数=原码,负数=反码
二的补码(two’s complement) 指的就是通常所指的补码
Legal ops: ! ~ & ^ | + << >>
Max ops: 4
Rating: 1
实现思路:
将1右移31位,送至最高位即可
实现代码:
int tmin(void) {//二进制的最小补整数10000000000000000000000000000000
return 1<<31;
}
(7) fitsBits
函数功能:判断一个整型能否被n位二进制补码表示
实现要求:
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
实现思路:
能否用n位表示,我们可以将x左移32-n然后右移32-n位,将x的符号全部填充为第n位的二进制数,如果说这个结果和x相等,那么说明可以表示,否则不能表示。
实现代码:
int fitsBits(int x, int n) {
int y;
n=(~n)+1;
y=(x<<(32+n))>>(32+n);//得到符号扩展的x
return !(x^y);
}
(8) divpwr2
函数功能:计算x/(2^n)的值并且向0取整
实现要求:
divpwr2 - Compute x/(2^n), for 0 <= n <= 30
Round toward zero
Examples: divpwr2(15,1) = 7, divpwr2(-33,4) = -2
Legal ops: ! ~ & ^ | + << >>
Max ops: 15
Rating: 2
实现思路:
这里实质上以做右移n位的操作,但是需要区分是正数还是负数,如果是正或0直接右移n位,为负数则需要加上偏置量后再进行移位操作。其中偏置为2^n-1.
实现代码:
int divpwr2(int x, int n) {
int temp=((1<<n)+(~0))&(x>>31);
return (x+temp)>>n;
}
(9) negate
函数功能:计算负数
实现要求:
negate - return -x
Example: negate(1) = -1.
Legal ops: ! ~ & ^ | + << >>
Max ops: 5
Rating: 2
实现思路:补码操作,取反加1
实现代码:
int negate(int x) {
return (~x)+1;
}
(10) isPositive
函数功能:判断一个数是否是正数返回1,否则返回0
实现要求:
isPositive - return 1 if x > 0, return 0 otherwise
Example: isPositive(-1) = 0.
Legal ops: ! ~ & ^ | + << >>
Max ops: 8
Rating: 3
实现思路:
右移取符号填充,然后要求x非零,y为0
实现代码:
int isPositive(int x) {
int y=x>>31;
return (!!x)&(!y);
}
(11) isLessOrEqual
函数功能:判断x<=y,是则返回1否则返回0
实现要求:
isLessOrEqual - if x <= y then return 1, else return 0
Example: isLessOrEqual(4,5) = 1.
Legal ops: ! ~ & ^ | + << >>
Max ops: 24
Rating: 3
实现思路:
判断x<=y,我们可以做减法。首先计算y-x然后将其右移31位取符号,取y的符号ys,取y^x的值的符号s,然后要求x<=y就必须满足:xy同号并且相减结果为正,或者xy异号,但y为正
实现代码:
int isLessOrEqual(int x, int y) {
//同号相减,异号y为正
int sub=(y+(~x)+1)>>31;
int ys=(y>>31);
int s=(y^x)>>31;
return (!s&!sub)|(s&!ys);
}
(12) ilog2
函数功能:计算x对2的对数并且向下取整
实现要求:
ilog2 - return floor(log base 2 of x), where x > 0
Example: ilog2(16) = 4
Legal ops: ! ~ & ^ | + << >>
Max ops: 90
Rating: 4
实现思路:
采用二分的方法来进行处理,首先选择中间的第16位,得到结果的逻辑值乘以16,然后处理logs+8右移8位乘以8.一次类推直到为1
实现代码:
int ilog2(int x) {
int logs=(!!(x>>16))<<4;
logs=logs+((!!(x>>(logs+8)))<<3);
logs=logs+((!!(x>>(logs+4)))<<2);
logs=logs+((!!(x>>(logs+2)))<<1);
logs=logs+(!!(x>>(logs+1)));
return logs;
}
(13) float_neg
函数功能:返回一个unsigned表示浮点数的负数
实现要求:
float_neg - Return bit-level equivalent of expression -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 representations of
single-precision floating point values.
When argument is NaN, return argument.
Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
Max ops: 10
Rating: 2
实现思路:
浮点数的负数实现很久暗淡,但是需要注意的是对于一个浮点数,他可能是nan,也可能是正负无穷大,也可能是一个可以正常表示的浮点数。对于nan,我们需要直接返回这个值,否则取反符号位。
实现代码:
unsigned float_neg(unsigned uf) {
unsigned int a=0xff;
unsigned int b=1<<31;
unsigned int exp=(uf>>23)&a;//取出阶码
unsigned int frac=uf&0x7fffff;//去除尾数
if(!(exp^a)&&frac) return uf;//当nan,返回uf否则返回-uf
return uf^b;
}
(14) float_i2f
函数功能:返回一个int数据强制转换位浮点数的unsigned类型表示
实现要求:
float_i2f - Return bit-level equivalent of expression (float) x
Result is returned as unsigned int, but
it is to be interpreted as the bit-level representation of a
single-precision floating point values.
Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
Max ops: 30
Rating: 4
实现思路:
首先我们判断是否为0,如果是那么直接返回。否则计算阶的值exp,如果exp小于等于23,说明可以正常表示,我们只需将尾数左移23-exp即可。如果exp>23,则发生位的截取,这个时候我们要判断是四舍五入还是向偶数取整。我们可以在截取位置加上1。之后我们判断以前的截取位置,如果不为1,我们减去1,如果是四舍五入产生的那么因为减法的借位是不会影响到frac的,如果是向偶数舍入也不会影响。最后我们将exp加上这个尾数,处理有可能产生的向exp进位,然后返回和并值。
实现代码:
unsigned float_i2f(int x) {
if(!x) return 0;//为0返回
else{
int exp=~0;//阶初始化为-1
unsigned temp1=x;
unsigned temp2,temp3;
int shift;
int sign=0x80000000&x;
if(sign) temp1=-temp1;
temp3=temp2=temp1;
while(temp1){
exp=exp+1;
temp1=temp1>>1;
}
shift=23-exp;//计算要移位的长度
if(shift>=0) temp3=temp3<<shift;//shift为正,表示可以用23位表示
else//否则发生截取,考虑舍入
{
shift=-shift;
temp2=temp2>>shift;//右移shift位
temp3+=(1<<(shift-1));//加上低位的进位
if(!(temp2&1)) temp3+=-1;//如果最低位不为1,说明无法发生偶数进位,所以减去1。如果四舍五入,则不会借位成功舍入,否则不发生进位
temp3=temp3>>shift;//右移shift
}
exp=((exp+0x7f)<<23)+(temp3>>1);//加上temp3可能发生向exp的进位
exp=exp&0xff800000; //清零frac
temp3=temp3&0x7fffff;//取后23位
return sign|exp|temp3;}//合并
}
(15) float_twice
函数功能:返回一个unsigned表示的浮点数*2的结果
实现要求:
float_twice - 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
实现思路:
这个函数实现并不复杂,我们首先对数据进行分类,如果数据是一个nan或者是无穷大,那么我不能改变它的值,直接返回,否则要判断数据是不是非规格化数,如果是那么将尾数位左移1位,如果是规格化数,我们可以直接将exp+1,但是需要注意的是如果因为exp+1产生了exp=0xff,那么结果因该是无穷大而不是nan,所以这种情况下我们要将frac尾数清零。最后注意合并。
实现代码:
unsigned float_twice(unsigned uf) {
unsigned int s=uf&0x80000000;
unsigned int exp=uf&0x7f800000;
unsigned int frac=uf&0x7fffff;
if(exp==0x7f800000)//Nan and inf
{
return uf;
}
else if(!exp)//非规格化数
{
frac<<=1;
}
else//规格化数,直接exp+1,同时如果exp全1那么需要对frac清零防止nan
{
exp+=(1<<23);
if(exp==0x7f800000) frac=0;
}
return (s|exp|frac);
}
实验结果及分析:
(1) 输入:命令./dlc bits.c和./dlc -e bits.c查看语法错误并检测操作符消耗:
(2) 编译并执行程序:输入命令make btest编译,指令./btest执行:
结果分析:
编译之后,我们的15个函数的操作符资源消耗分别为4,3,7,35,5,1,8,7,2,5,13,27,8,27,11满足程序的设计要求,并且通过编译,无警告和错误。运行之后各项得分分别为1,2,3,4,4,1,2,2,2,3,3,4,2,4,4。最后得分为41.程序设计满足实验要求。
收获与体会:
本次实验,我做了大概有一个下午吧,有疑问,有难过当然更有收获也有惊奇,当把我们学过的与运算通过摩根定律转换时,体会到了知识的相通性。取指定字节是,由于一个字节有8位,故实际要移动n<<3位,然后,再让原来的x向右移位,机科取出相应的字节。逻辑右移的过程中要注意消除符号位,所以右移的过程中先凑出来一个0000,然后与x相与即可得到逻辑右移。计算二进制1的过程较为巧妙,刚开始用的while循环,然后,每次让x向右移位,当然了这样会在成超时,于是乖乖用二分法,先找出2位中的1,然后4中的1,接着8位中的1。最后,是int转为unsigned ,以及unsigned转为float*2 要做的都是把它转为规格化数,否则要判断数据是不是非规格化数,如果是那么将尾数位左移1位。总之,在遇到问题的时候,不要害怕问题,要多去尝试寻找解决方法,也就能达到解决问题的目的
图片转存中…(img-tFs7GaeK-1618330307679)]](https://imgtu.com/i/cy27RJ)
结果分析:
编译之后,我们的15个函数的操作符资源消耗分别为4,3,7,35,5,1,8,7,2,5,13,27,8,27,11满足程序的设计要求,并且通过编译,无警告和错误。运行之后各项得分分别为1,2,3,4,4,1,2,2,2,3,3,4,2,4,4。最后得分为41.程序设计满足实验要求。
收获与体会:
本次实验,我做了大概有一个下午吧,有疑问,有难过当然更有收获也有惊奇,当把我们学过的与运算通过摩根定律转换时,体会到了知识的相通性。取指定字节是,由于一个字节有8位,故实际要移动n<<3位,然后,再让原来的x向右移位,机科取出相应的字节。逻辑右移的过程中要注意消除符号位,所以右移的过程中先凑出来一个0000,然后与x相与即可得到逻辑右移。计算二进制1的过程较为巧妙,刚开始用的while循环,然后,每次让x向右移位,当然了这样会在成超时,于是乖乖用二分法,先找出2位中的1,然后4中的1,接着8位中的1。最后,是int转为unsigned ,以及unsigned转为float*2 要做的都是把它转为规格化数,否则要判断数据是不是非规格化数,如果是那么将尾数位左移1位。总之,在遇到问题的时候,不要害怕问题,要多去尝试寻找解决方法,也就能达到解决问题的目的