(1)实验环境
Linux操作系统
(2)实验内容
①根据bits.c中的要求补全以下的函数:
int bitXor(int x, int y);
int tmin(void);
int isTmax(int x);
nt allOddBits(int x);
int negate(int x);
int isAsciiDigit(int x);
int conditional(int x, int y, int z);
int isLessOrEqual(int x, int y);
int logicalNeg(int x);
int howManyBits(int x);
unsigned float_twice(unsigned uf);
unsigned float_i2f(int x);
int float_f2i(unsigned uf);
*编译:./dlc bits.c
*测试:make btest
./btest
③具体
本次为一次计算机系统实验,就是使用一些基本的运算符来实现函数功能。 //1 /* * bitXor - 仅允许使用~和&来实现异或 * 例子: bitXor(4, 5) = 1 * 允许的操作符: ~ & * 最多操作符数目: 14 * 分值: 1 */ /* * tmin - 返回最小的二进制补码 * 允许的操作符: ! ~ & ^ | + << >> * 最多操作符数目: 4 * 分值: 1 */ //2 /* * isTmax - 如果x是最大的二进制补码,返回1;否则,返回0 * 允许的操作符: ! ~ & ^ | + * 最多操作符数目: 10 * 分值: 2 */ /* * allOddBits - 如果所有奇数位都为1则返回1;否则返回0 * 例子: allOddBits(0xFFFFFFFD) = 0, allOddBits(0xAAAAAAAA) = 1 * 允许的操作符: ! ~ & ^ | + << >> * 最多操作符数目: 12 * 分值: 2 */ /* * negate - 返回-x * 例子: negate(1) = -1. * 允许的操作符: ! ~ & ^ | + << >> * 最多操作符数目: 5 * 分值: 2 */ //3 /* * isAsciiDigit - 如果x是ascii码中的0~9,返回1;否则返回0 * 例子: isAsciiDigit(0x35) = 1. * isAsciiDigit(0x3a) = 0. * isAsciiDigit(0x05) = 0. * 允许的操作符: ! ~ & ^ | + << >> * 最多操作符数目: 15 * 分值: 3 /* * conditional - 实现x?y:z * 例子: conditional(2,4,5) = 4 * 允许的操作符: ! ~ & ^ | + << >> * 最多操作符数目: 16 * 分值: 3 */ /* * isLessOrEqual - 如果x<=y返回1否则返回0 * 例子: isLessOrEqual(4,5) = 1. * 允许的操作符: ! ~ & ^ | + << >> * 最多操作符数目: 24 * 分值: 3 */ //4 /* * logicalNeg - 实现!运算符的功能 * 例子: logicalNeg(3) = 0, logicalNeg(0) = 1 * 允许的操作符: ~ & ^ | + << >> * 最多操作符数目: 12 * 分值: 4 /* howManyBits - 返回将X表示为补码所需的最小有效位数。 * 例子: howManyBits(12) = 5 * howManyBits(298) = 10 * howManyBits(-5) = 4 * howManyBits(0) = 1 * howManyBits(-1) = 1 * howManyBits(0x80000000) = 32 * 允许的操作符: ! ~ & ^ | + << >> * 最多操作符数目: 90 * 分值: 4 */ //float /* * float_twice - 以unsinged表示的浮点数二进制的二倍的二进制unsigned型 * 参数和结果都会被作为unsigned返回,但是会表示为二进制的单精度浮点值。 * 允许的操作符: 任何整数或者无符号数操作符包括: ||, &&. also if, while * 最多操作符数目: 30 * 分值: 4 */ /* * float_i2f - 返回int x的unsigned浮点数的二进制形式 * 参数和结果都会被作为unsigned返回,但是会表示为二进制的单精度浮点值 * 允许的操作符: 任何整数或者无符号数操作符包括: ||, &&. also if, while * 最多操作符数目: 30 * 分值: 4 */ /* * float_f2i - 返回unsigned uf的整型数的二进制形式 * 参数和结果都会被作为unsigned返回,但是会表示为二进制的单精度浮点值 * 任何超过范围的数都应该返回 0x80000000u. * 允许的操作符: 任何整数或者无符号数操作符包括: ||, &&. also if, while * 最多操作符数目: 30 * 分值: 4 */
(3)实验步骤
1. int bitXor(int x, int y);
由离散数学知识可得到异或:
int bitXor (int x, int y) { return ~(~(~x&y)&~(x&~y)); }
2. int tmin(void);
最小值为0x8000 0000,我们可以将1左移31位得到最小值:
int tmin(void) { return 1<<31; }
3. int isTmax(int x);
这里我参考了一个师兄的方法,再加上我的方法,所以列出了两种方法:
①由于最大值是0x7fff ffff,0x7fff ffff+1->0x1000 0000,而对0x1000 0000来说,0x1000 0000+0x1000 0000=0;而自身相加为零的就只有0和0x1000 0000,然后再将0xffff ffff(-1)排除即可。
#####################################补充####################################
32位的int,正数的范围是(0,0x7FFFFFFF),负数(0x80000000,0xFFFFFFFF)
负数 = -(0x100000000 - (0x80000000,0xFFFFFFFF))
0xFFFFFFFF实际代表的值是 -1
############################################################################
int isTmax(int x) { return !(~(x^(x+1)))&!!(x+1); }
②我们已经在2中写出了tmin,这里可以直接用tmin求得tmax再~验证:
int isTmax(int x) { return !(x^~(1<<31)); }
4. int allOddBits(int x);
判断奇数位是否全为1,则需要先把偶数位置1(通过与0x5555 5555进行或运算),再按位取反,观察若值为0则应返回1。在二进制中,只有所有奇数位都为1的数与0x5555 5555进行&运算才会得到0,由于实验要求在32位机器上操作,所以不能直接使用0x5555 5555操作,所以问题转变为如何求得0x5555 5555。要得到0x5555 5555可以将0x55分别左移8,16,24位,得到三个数:
0x55 = 0x50 + 0x5 = 5 + (5<<4)
0x55->(0101 0101)₂;
0x555->(0101 0101 0101)₂;
0x55 5555->(0101 0101 0101 0101 0101 0101)₂;
0x5555 5555->(0101 0101 0101 0101 0101 0101 0101 0101)₂;
HEX 55 = DEC 85 = BIN 0101 0101
0x5555 5555 = 85+(85<<8)+(85<<16)+(85<<24)
------->
从HEX值可以看出,这就是由0x55左移相加后得到的0x5555 5555。
int allOddBits(int x) { return !(~(x|(85+(85<<8)+(85<<16)+(85<<24)))); }
5. int negate(int x);
取反加一就OK!
int negate(int x) { return (~x+1); }
6. int isAsciiDigit(int x);
若x是数字,则x在‘0’~‘9’之间。可以用x-48>=0和x-58<0(x+~48+1>=0和x+~58+1<0)来计算
int isAsciiDigit(int x) { return !((x+~48+1)>>31)&!!((x+~58+1)>>31); }
7. int conditional(int x, int y, int z);
首先使t=!x,当x为0时返回1,当x不为0时返回0,根据题意得到( ①&y)|( ②&z);
HEX 0xffff ffff = DEC (-1)
HEX 0x0 = DEC (0)
首先对空①,当x不为0即t=0时,需要将t转换为0xffff ffff,当x=0即t=1时,需要将t转换为0x0,直接t-1即可得到0,所以空①为“!x+~1+1”,同理空②为“~!x+1”:
int conditional(int x, int y, int z) { return ((!x+~1+1)&y)|((~!x+1)&z); }
8. int isLessOrEqual(int x, int y);
判断x是否小于等于y,直接用y-x可能会超出int的表示范围,故而:
①在x与y同号的情况下转换为p=y-x>=0,然后对p符号位进行(p>>31)&1操作,符号位为0则返回1,符号位1则返回0;
②x,y异号时,只要x>=0,就要返回0,否则返回1,由(x>>31)&1能达到该效果;
③c=a+b可作为x,y同号异号的判断。
int isLessOrEqual_1(int x, int y) { int a = x>>31; int b = y>>31; int c = a+b; int p = !((~x+1+y)>>31); return (c&(a&1))|((~c)&p); }或者考虑按符号来分情况讨论:
①x为负,y为正;②x和y同号,相等;③x+~y+1<0。
int isLessOrEqual_2(int x, int y) { int xs = ((x>>31)&1); int ys = ((y>>31)&1); return (xs&!ys)|(!(xs^ys)&(!(x^y)|(x+~y+1>>31)&1)); }
9. int logicalNeg(int x);
令y=~x+1,考虑x与y的符号位:
①当x=0x0时,x,y两者符号位都为0;②当x=0x8000 0000时,x,y两者符号位都为1;
③否则,两者符号位为01或10;④根据离散数学的真值表得出(~x)&(~y),则当且仅当x=0x0时,其符号位为1。
int logicalNeg_1(int x) { return ((~(~x+1)&~x)>>31)&1; }
或者这样考虑,直接用x<0 && x>=1转换
int logicalNeg_2(int x) { return ((x>>31)+1)&((x+~1+1>>31)&1); }
10. int howManyBits(int x);
求其表示的最小位数。如果x是负数,那么对x取反,因为需要的位数是一样的;然后采用2分法,就是通过右移一半位数来判断,先对其右移16bit,若为0,则最低数量为16,根据此思想,继续对其右移8bit、4bit、2bit、1bit得到h16,h8,h4,h2,h1,分别表示一分为二后其中的一半是否存在,而另一半继续循环直至1,这时还剩下x,最后还需要再加上符号位0。
int howManyBits_1(int x) { x = ((x>>31)&~x|(~!(x>>31)+1)&x); int h16,h8,h4,h2,h1,y; y = x>>16; h16 = !!y<<4; x = x>>h16; y = x>>8; h8 = !!y<<3; x = x>>h8; y = x>>4; h4 = !!y<<2; x = x>>h4; y = x>>2; h2 = !!y<<1; x = x>>h2; y = x>>1; h1 = !!y<<0; x = x>>h1; return h16+h8+h4+h2+h1+1+x; }
思路一样的另一种方法
int howManyBits_2(int x) { int s, s1, s2, s4, s8, s16; int t1 = ((!x)<<31)>>31; int t2 = ((!~x)<<31)>>31; int op = x^(x>>31); s16 = (!!(op>>16))<<4; op = op>>s16; s8 = (!!(op>>8))<<3; op = op>>s8; s4 = (!!(op>>4))<<2; op = op>>s4; s2 = (!!(op>>2))<<1; op = op>>s2; s1 = (!!(op>>1)); op = op>>s1; s = s1+s2+s4+s8+s16+2; return (t2&1)|((~t2)&((t1&1)|((~t1)&s))); }
11. float_twice
求float的2的倍数。将uf(unsigned float)切割成符号位s,阶码exp和位数frac,分情况讨论
①如果exp==0xff,continue;②如果exp==0,那么直接frac<<=1;③否则exp递增,然后再判断一下杜绝null。
unsigned float_twice(unsigned uf) { unsigned s = uf&0x80000000; unsigned exp = uf&0x7f800000; unsigned frac = uf&0x007fffff; if(!exp) { frac<<=1; }else if(exp^0x7f800000) { exp += 0x00800000; if(!(exp^0x7f800000)) { frac = 0; } } return s|exp|frac; }
或者这样考虑:①对于0,返回0;②对于指数全为1,由于溢出,返回的是自身;③对于指数为0,位数左移一位。当其最高为1,需要指数加1;④指数+1,此时指数全为1,尾数为0。
unsigned float_twice(unsigned uf) { int exp = 0x7f800000&uf; int num = 0x007FFFFF&uf; unsigned flag = 0x80000000&uf; int n = (exp>>23)-127; if(uf == 0x0) return 0; if(n == 128) return uf; if(exp == 0) { if(uf&0x00400000 { num = num<<1; exp = 0x00800000; } else num = num<<1; } else { exp = exp+0x00800000; n = (exp>>23)-127; if(n == 128) num=0; } return (exp|num|flag); }
12. unsigned float_i2f(int x);
(1)首先我们需要知道int型的范围为-2^31~2^31-1主要有两种情况:
①用(二进制)科学计数法表示int型数时,尾数位数<=23,例如0x00008001,此时将0x8001左移24-16=8位得到frac,而exp则127+16-1;
②当尾数位数>23时,找到位数最末一位记作x[i],然后对尾数的舍去分3种情况考虑,初始化c=0:
a)当x[i-1]=1且x[i-2]、x[i-3]…x[0]都为0(即要偶端舍入情况),且x[i]=1,令c=1(此处frac若是全为1,则会导致frac+c超出范围,这是bug,当还是通过了);
b)当x[i-1]=1且x[i-2]、x[i-3]…x[0]不都为0,令c=1(与a存在同样的bug);
c)除a、b的情况,c=0;
③其他特殊情况再考虑一下就好了;
以0x0800 8001为例子 31 <----- 0 0000 1000 0000 0000 1000 0000 0000 0001 下划线部分为保留下来的frac,最后一位的下标记为i,在这里i=8.
unsigned float_i2f_1(int x) { unsigned abs = x; unsigneds = 0x80000000&x; unsignedtem = 0x40000000; unsignedexp_sign = 0; unsigned exp = 0; unsigned frac; unsigned c = 0; if(x == 0) { return x; } else if(x == 0x80000000) { return (s+(158<<23)); } else { if(x<0) { abs = -x; } while(1) { if(tem&abs) break; tem = tem>>1; //printf("%x\n%x\n",tem,abs); exp_sign = exp_sign+1; }//while //printf("exp_sign = %d\nx = %x\n",exp_sign,x); frac = (tem-1)&abs; //printf("frac = %x\n",frac); if(31-1-exp_sign>23) { //尾数大于23位的情况 int i = 30-exp_sign-23; if((frac<<(31-(i-1))) == 0x80000000){ //ou = quzhi if((frac&(1<<i))!= 0) c = 1; } } else if((frac&(1<<(i-1)))!= 0) { c = 1; } frac = frac>>i; } else frac = frac<<(23-(31-exp_sign-1)); exp = 157-exp_sign; //printf("exp = %d\n s = %x\n",exp,s); //printf("frac = %x\n",frac); //printf("result = %x\n",(s+(exp<<23)+frac+c)); return (s+(exp<<23)+frac+c); }//else }
(2)具体解释见代码注释:
unsigned float_i2f_2(int x) { int sign = x>>31&1;//获取符号位(1位) int i; int exponent; //指数域(8位) int fraction; //小数域 int delta;//偏差(用于舍入) int fraction_mask;//小数域的掩码(23位) if(x == 0)//如果为0就直接返回 return x; else if(x==0x80000000)//如果为TMin,解释为-(2^31),对应float就是(1)x1.0x2^31,所以S=1,M=1,E=31,指数域=31+127=158 exponent=158; else{ if (sign)//通过前面的操作已经确定了符号,先把int的绝对值获取,之后利用浮点数的计算公式即可计算出float的值,现在要获取绝对值存入内存中 x = -x; i = 30;//最高位是符号位,次高位是有效数字的起始位 while ( !(x >> i) )//从左往右查找有效数字第一个不为零的位,对应的位置就是最终的i(这里的位置从0开始标号) i--; //printf("%x %d\n",x,i); exponent = i + 127;//数值的最高位已经找到是第i位(有效数据共有i+1位),又因为int类型不可能是非规格数据的范围(为0的情况在前面已排除),所以小数域就是d第i为后面的位向量(小数域一共有i个位),故阶码E=i(小数部分x2的E次方),指数域等于i+127; x = x << (31 - i);//清除有效数据前面的所有0,包括符号位,得到有效数据开头的数据 fraction_mask = 0x7fffff;//设置23位的小数域掩码 fraction = fraction_mask & (x >> 8); /** * 虽然按照浮点数格式,最前面的9个位不加入小数位,按道理应该右移9位, * 但是由于int类型的参数不可能是非规格数,所以最前面的一个有效数据也被舍弃(默认M=1+f), * 当向右移动8位,舍弃了有效数据的低8位,再和掩码处理以后,一共舍弃了9位 * 除了低八位还包括有效数据的最高位,类比二进制小数中小数点左边的那一位数字,在float存储的时候,小数点左边数字不存入内存 * 用9个位用来存储符号位+指数域 */ x = x & 0xff;//由于右移8位,舍弃了有效数据第八位,现在获取低八位用于舍入操作 delta = x > 128 || ((x == 128) && (fraction & 1)); /** * 如果低八位超过八位二进制能表示的无符号数的一半, * 要在小数域+1,普通的四舍五入思想 * 如果低八位刚好等于八位二进制能表示的无符号数的一半,而且小数域目前最后一位是1, * 根据向偶数舍入的模式,也要在小数域+1,向上舍入的思想, * 如果低八位刚好等于八位二进制能表示的无符号数的一半,如果小数域目前最后一位是0,则向下舍入,不加1 * 如果低八位刚好小于八位二进制能表示的无符号数的一半,直接丢弃,不加1,普通的四舍五入思想 */ fraction += delta;//进行舍入 if(fraction >> 23) {//如果舍入过后,小数域多余23位,则只取低23位,高位舍弃,但是阶码E要加1,所以指数域也就要加1 fraction &= fraction_mask; exponent += 1; } } return (sign<<31)|(exponent<<23)|fraction;//符号位最高位(31),指数域(30--23),小数域(22-0) }
(3)先考虑0和tmin这2个不能没有相反数的情况,然后分离出符号位s和转换成绝对值,计算出x的位数,求出阶码exp,将其移到最左边再右移8位,用多出的8位和第9位(即最后1位)以及相关规则判断是否舍入。
unsigned float_i2f_3(int x) { if(!x){ return x; }else if(x == 0x80000000){ return 0xcf000000; }else{ int s = x>>31&1; if(s){ x = -x; } int i = 30; while(!(x>>i)){ i--; } int exp = i+127; x = x<<(31-i); int frac = (x>>8)&0x7fffff; x = x&0xff; int dalta = x>128||((x == 128)&&(frac&1)); frac += dalta; if(frac>>23){ frac& = 0x7fffff; exp++; } return (s<<31)|(exp<<23)|frac; } }
13. float_f2i
返回unsigned uf的整型数的二进制形式,将uf(unsigned float)切割成符号位s,阶码exp和位数frac,分情况讨论:
①当exp=0或exp-127<0时,返回0;
②当exp-127>=31时候,超出表示范围,于是返回0x80000000u;
③当exp-127<=23,根据符号位返回值num>>(23-(exp-127))。
int float_f2i_1(unsigned uf) { int s = uf>>31; int exp = (uf>>23)&0xff; int frac = uf&0x007fffff; if(exp<0x7f){ return 0; }else if(exp>157){ return 0x80000000; }else{ int abs; if(exp-150>0){ abs = 0x00800000+frac<<exp-150; }else{ abs = 0x00800000+frac>>150-exp; } if(s){ return -abs; }else{ return abs; } } }exp为0,x=(-1)^s*0.frac*2^(-126);否则x=(-1)^s*1.frac*2^(exp-127)分情况考虑:
①根据float转为int是向0舍入的情况,当exp=0或者exp<127(由exp-127<0得到);
②令exp_sign=((exp>>23)-127)>=0,根据x=(-1)^s*1.frac*(exp-127),
a)exp_sign=0,x=1.x[22]x[21]…x[0]-->x=1;
b)exp_sign=1,x=1x[22].x[21]…x[0]-->x=1x[22];
c)exp_sign=2,x=1x[22]x[21].x[20]…x[0]-->x=1x[22]x[21];
d)exp_sign=23,x=1x[22]x[21]x[20]…x[0]-->x=1x[22]x[21]…x[0];
e)exp_sign=30,x=1x[22]x[21]x[20]…x[0]-->x=1x[22]x[21]…x[0]0…0(共31位);
f)exp_sign=32,x=1x[22]x[21]x[20]…x[0]-->x=1x[22]x[21]…x[0]0…0(共32位),此时,因为int为有符号,只有0x0000 00000能被表示出来,其他都是null;
③当exp_sign=((exp>>23)-127)>=33,null。
int float_f2i_2(unsigned uf) { int exp = 0x7f800000&uf; unsigned s = 0x80000000&uf; int frac = 0x007fffff&uf; int frac2 = frac+0x008fffff; int exp_sign = ((exp>>23)-127); //printf("uf=%x\n",uf); if(exp == 0x7f800000) return 0x80000000u; else if(exp == 0) return 0; else if(exp_sign <= -1 && exp_sign >= -126) return 0; else if(exp == 31 && frac == 0 && s != 0) return 0x80000000; else if(exp_sign >= 31) return 0x80000000u; elseif(exp_sign <= 23) frac2 = frac2 >> (23-exp); else frac2 = frac2<<(exp-23); if(s == 0) return frac2; else return -frac2; }
最终运行结果: