Datalab总结


基础运算

将难度分为5个档次,仅供参考。
要求全程可以直接使用的数范围在0x00-0xFF之间

1.异或

要求:仅用~和&实现异或运算;
难度:1星
思路:直接采用数字逻辑中学到的就可以推导出计算公式:
在这里插入图片描述
代码表达如下:

/* 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) {
    int a1, a2, re;
    a1 = ~(x & (~y));
    a2 = ~((~x) & y);
    re = ~(a1 & a2);
  return re;
  
//不使用中间变量:
int bitXor(int x, int y) {
  return  ~( (~(x & (~y))) & (~((~x) & y)));
}

2.判断是否为最大数

要求:x为最大数,返回1;否则返回0。
难度:3星
思路:(这一题没有想到特别好的思路,如果有好的想法欢迎分享)
最大数为0x7FFFFFFF,最开始发现最大数加1后的值与最大数异或可以得到全1,所以用此作为判断依据。

后来发现还有一个特例也会有相同结论:0xFFFFFFFF,所以还需要排除全1的这种情况。

可以发现(其实后来都是凑出来的orz),全1加上1后是全0,与最大数加上1得到1不同,所以以此作为切入点进行判断。

语言难以表达,上代码:

/* 2
 * judgeTMax - returns 1 if x is the maximum of two's 	 *complement number, and 0 otherwise 
 *   Legal ops: ! ~ & ^ | +
 *   Max ops: 10
 *   Rating: 1
 */
 //便于理解版:
int judgeTMax(int x) {
    int a1, a, b, c;
    a1 = !(x + 1) + 1;
    //如果x为最大数,a1就是1,下述判断方法与最初想的一样;
    //如果x为全1,a1是2,则不会干扰判断;
    //主要改善的点在与把x+1改为了x+a1
    //而a1是通过找到全1和最大数之间不同点入手构造的一个掩码。
    a = x + a1;
    b = ~(a ^ x);
    c = !b;
  return c;
}

//去掉中间变量,最终代码:
int judgeTMax(int x) {
  return !(~((x + (!(x + 1) + 1)) ^ x));
}

3.判断是否偶数位全1

要求:判断给定二进制补码的偶数位是不是全为1(从右向左数,从0开始计数的偶数位)
难度:3星
思路:
1.先构造出偶数位全为1,奇数位全为0的数0x55555555,构造方法为通过0x55,不断进行左移再加上原来的数。
2.进行判断时先将0x55555555和x进行与操作,使得x的奇数位全为0,偶数位保持不变,排除奇数位对判断的影响。
3.再将2中得到的数和0x55555555进行异或操作,如果x中存在偶数位不为1,则得到的结果中对应的位为1——不是全0;如果x满足条件,偶数位全1,则得到结果全0。进行!得到答案。

代码如下:

/* 3
 * allEvenBits_one - return 1 if all even-numbered bits in an integer are set to 1
 *   where bits are numbered from 0 (least significant) to 31 (most significant)
 *   Examples allEvenBits_one(0xFFFFFFFD) = 1, allEvenBits_one(0xA5555555) = 0
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 12
 *   Rating: 2
 */
//便于理解版:
int allEvenBits_one(int x) {
    int a = 0x55,b,c,d,e,y;
    b = (a << 8);
    c = a + b;
    d = (c << 16);
    e = c + d;
    y = !((e & x)^e);
  return y;
}

//最终代码:
int allEvenBits_one(int x) {
    int a = ((0x55 << 8) + 0x55);
    mask = (a << 16) + a;
    return !((mask & x) ^ mask);
}

4.相反数运算

难度:1星
思路:求对应的相反数就是取反加1
代码入下:

/* 4
 * negative_transform - return -x 
 *   Example: negative_transform(1) = -1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 5
 *   Rating: 2
 */
int negative_transform(int x) {
  return (~x+1);
}

5.判断给定的x是否满足条件0x30 <= x <= 0x39

难度:2星
思路:
观察0x30-0x39可以发现,0x30-0x37最后六位都是110xxx形式,后面三位数取任何值都落在该区间上;0x38和0x39则为11100x形式;
由于x取任何值都可以,故只需要对110和11100进行判断。
判断方法是移位然后异或。

代码如下:

/* 5
 * judgeAsciiDigit - return 1 if 0x30 <= x <= 0x39 (ASCII codes for characters '0' to '9')
 *   Example: judgeAsciiDigit(0x35) = 1.
 *            judgeAsciiDigit(0x3a) = 0.
 *            judgeAsciiDigit(0x05) = 0.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 15
 *   Rating: 3
 */
 //便于理解版:
int judgeAsciiDigit(int x) {
    int zerotoseven = !((x>>3) ^ 0x06);
    int eightandnine = !((x>>1) ^ 0x1c);
    int re = zerotoseven | eightandnine;
    return re;
}

//最终代码:
int judgeAsciiDigit(int x) {
    return (!((x>>3) ^ 0x06)) | (!((x>>1) ^ 0x1c));
}

6.条件运算符

目的:通过位运算实现条件运算符
难度:3星
思路:
需要构造一个掩码全0或全1,通过和y,z做与运算进行选择。
所以构造这样一个mask:当x == 0 时,mask为全1,将z选择出来;当x非0时,mask全0,用~mask将y选择出来。

/* 6
 * contitionExpr - same as x ? y : z 
 *   Example: contitionExpr(2,4,5) = 4
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 16
 *   Rating: 3
 */
 //便于理解版:
int contitionExpr(int x, int y, int z) {
    int mask;
    mask = ~(!x) + 1;//当x == 0 时,mask为全1;当x非0时,mask全0;
    int re = (y & ~mask) + (z & mask);//当x == 0 时,mask为全1,将z选择出来;当x非0时,mask全0,~mask将y选择出来。
    return re;
}

//最终代码:
int contitionExpr(int x, int y, int z) {
    return  (y & ~(~(!x) + 1)) + (z & (~(!x) + 1));
}

7.逻辑非运算

目的:通过位运算实现逻辑非运算
难度:2星
思路:逻辑非运算主要用与判别0和非0,所以考虑0 和非0 的不同点。
x为0时,x = -x = 0,符号位均为0
x不为0时,除了0x80000000外其余的数均满足 x不等于-x
考虑0x800000000的特殊情况,此时满足 -x = x 但符号位均为1

所以当 x = -x 且符号位均为0时返回1,其余时候返回0;关键是判断符号位,最后的判断有利用前面第六题条件运算符的结果

/* 7
 * logicalNot - implement the ! operator, using all of 
 *              the legal operators except !
 *   Examples: logicalNot(3) = 0, logicalNot(0) = 1
 *   Legal ops: ~ & ^ | + << >>
 *   Max ops: 12
 *   Rating: 3 
 */
 //便于理解版:
int logicalNot(int x) {
    int a1,a2;
    a1 = (x >> 31);
    a2 = ((~x + 1) >> 31);
    //只有当x == 0时,a1和a2都是0,其余情况至少存在一个全1
    int a3 = a1|a2;//只有当x == 0时,a3是全0,其余情况全1
    int re = (0x01 & ~a3) + (0x00 & a3);//利用条件运算符的表达式
  return re;
}

//最终代码:
int logicalNot(int x) {
    int mask = (x >> 31)|((~x + 1) >> 31);
  return (0x01 & ~mask) + (0x00 & mask);
}

8.计算最少可以表达出x的二进制位数

难度:5星
思路:有两个难点,一个是需要找到负数和正数的统一处理方法,另一个是需要找到求出具体位数的简便方法(不能用循环)。

对于第一个难点:所有的负数取反之后都是正数(不加1),这样得到的正数的最少位数与原本负数的位数相同(可以通过验证得到,但是严谨的证明不是很清楚),所以可以将此问题转化为求正数的最小二进制位数。

对于第二个难点:通过类似二分法的方法解决。
下面只对其中第一步进行详细解释,剩下的可以类推:
1.用bit16 = !!(x >> 16) << 4来判断高16位是否有1。
2.因为!的优先级高于<<,所以先进行!!(x >> 16)。
3.由于都是对正数进行求解,所以在右移16位后,前16位全为0,后16位是原本的高16位。
4.如果高16位存在至少一位为1,则!(x>>16)为0则再进行一次!操作得到!!(x >> 16) = 1(0x00000001),否则!!(x >> 16) = 0x00000000
5.进行<<4的操作使得值变为0x00010000或者0x00000000,即16和0,目的是为了计数和下一步缩小判定范围。
6.如果高十六位为存在至少一位为1,则这个数至少有16位,接下来需要做的就是再进行进一步的定位来确定大于16位的部分还需要多少位,所以用x = x >> bit16;将x的低16位移走,将高16位移动到低16位的位置,对低16位(原本的高16位)进行判断
如果高16位中没有1,则继续对低16位进行判断,不用进行移位操作(bit16为全0)

以上就是第一步代码 bit16 = !!(x >> 16) << 4; x = x >> bit16; 的意思。

代码如下:

/* 8
 * leastBeats - return the minimum number of bits required to represent x in
 *             two's complement, remember the a bit of sign is needed.
 *  Examples: leastBeats(12) = 5 (most significant)
 *            leastBeats(298) = 10
 *            leastBeats(-5) = 4
 *            leastBeats(0)  = 1
 *            leastBeats(-1) = 1
 *            leastBeats(0x80000000) = 32
 *  Legal ops: ! ~ & ^ | + << >>
 *  Max ops: 90
 *  Rating: 4
 */
 //便于理解版:
int leastBeats(int x) {
    int sign,bit16,bit8,bit4,bit2,bit1,bit0,re;
    sign = x >> 31;//得到符号,如果符号位为0,sign全0,否则sign全1
    x = (x & ~sign) | ((~x) & sign);//将x统一变为对应(取反不加1)的正数
	//下面开始对x的位数进行判断计数
    bit16 = !!(x >> 16) << 4;//判断32位数中高16位是否有1。
    x = x >> bit16;
    bit8 = !!(x >> 8) << 3;//在低16位中判断高八位是否有1
    x = x >> bit8;
    bit4 = !!(x >> 4) << 2;//在低8位中判断高4位是否有1
    x = x >> bit4;
    bit2 = !!(x >> 2) << 1;//在低4位中判断高2位是否有1
    x = x >> bit2;
    bit1 = !!(x >> 1);//在低2位中判断高1位是否有1
    x = x >> bit1;
    bit0 = x; //判断最低位是否为1
    re = bit16 + bit8 + bit4 + bit2 + bit1 + bit0 + 1; // 正数需要符号位一位
  return re;
}

//最终代码也没啥变化(去掉了注释,bit0和re)
int leastBeats(int x) {
    int sign,bit16,bit8,bit4,bit2,bit1;
    sign = x >> 31;
    x = (x & ~sign) | ((~x) & sign);//将x统一变为正数来看
    bit16 = !!(x >> 16) << 4;//判断高16位是否有1
    x = x >> bit16;
    bit8 = !!(x >> 8) << 3;
    x = x >> bit8;
    bit4 = !!(x >> 4) << 2;
    x = x >> bit4;
    bit2 = !!(x >> 2) << 1;
    x = x >> bit2;
    bit1 = !!(x >> 1);
    x = x >> bit1;
  return bit16 + bit8 + bit4 + bit2 + bit1 + x + 1;
  }
  

9.判断1的总个数是否为奇数

难度:5星
思路:和有一个数字逻辑的表达也很相似,主要通过异或实现。
比如X = X1,X2,X3,X4是一个四位二进制数,则将X与X>>1异或之后得到的结果设为b1,b2,b3,b4,则的b4就存放的是X3和X4的奇偶性(奇数个1则b4 = 1),b2就存放的是X1和X2的奇偶性。奇数位无意义。

利用这个特性来求解,具体看代码:

/* 9
 * bitsOneOdd - return whether the number of 1 over all bits of an integer is odd or even
                if number is odd, return 1, else return 0
 * Examples: bitsOneOdd(1) = 1 0x01
 *           bitsOneOdd(2) = 1 0x10
 *           bitsOneOdd(3) = 0 0x11
 * Legal Ops: ! ~ & ^ << >>
 * MaxOps: 20
 * Rating: 4
 */
  //便于理解版:
int bitsOneOdd(int x){
    int a1,a2,a3,a4,a5,re;
    a1 = x ^ ( x >> 1 );
    //a1的偶数位存放着x中每两位的奇偶性
    a2 = a1 ^ ( a1 >> 2);
    //偶数位与偶数位相异或,a2中每四位中最低位得到a1中相邻偶数位的奇偶性
    a3 = a2 ^ ( a2 >> 4);
    //同理类推,a2中每四位中最低位与相邻的高四位中最低位相异或,a3中每8位中最低位得到a2中相邻每四位中最低位的奇偶性
    a4 = a3 ^ ( a3 >> 8);
    a5 = a4 ^ ( a4 >> 16);//最终最低位得到了x的奇偶性
    re = 0x01 & a5;//将其余位置0,取出最后一位作为结果,
  return re;
}

//最终代码去掉了re:
int bitsOneOdd(int x){
    int a1,a2,a3,a4,a5;
    a1 = x ^ ( x >> 1 );
    a2 = a1 ^ ( a1 >> 2);
    a3 = a2 ^ ( a2 >> 4);
    a4 = a3 ^ ( a3 >> 8);
    a5 = a4 ^ ( a4 >> 16);
  return 0x01 & a5;
}


整数运算

1.加法溢出判断

难度:2星
思路:负溢出就是两个负数相加变为0或者正数,正溢出就是两个正数相加变为负数。

具体代码如下:

/* 10
* plusFlow - return whether the sum of two integer x and y overflows(two's complement number)
* if overflow or underflow, return 1
* Examples: plusFlow(0xffffffff, 0x8000000)=1, plusFlow(1, 1)=0
* Legal Ops: + - ^ && || ! < > ==
* Maxops: 15
* Rating: 1
*/
int plusFlow(int x, int y){
    int sum = x + y;
    int neg_over = (x < 0 && y < 0) && (sum > 0 || sum == 0);
    int pos_over = (x > 0 && y > 0 && sum < 0);
    return neg_over ||  pos_over ;
}

2.减法溢出判断

难度:4星(其实感觉看起来不难,但当时做的时候好像不太行orz

具体代码如下:

/* 11
 * minusFlow - return whether the subtraction of two integer x and y overflows(two's complement number)
 * if overflow or underflow, return 1
 * Examples: minusFlow(0xffffffff, 0x80000000)=1, minusFlow(1, 2)=0
 * Legal Ops: + - ^ && || ! < > ==
 * Maxops: 15
 * Rating: 2
 */
int minusFlow(int x, int y){
    int sum = x - y;
    int over1 = (!(x < 0)) && y < 0 && ( sum < 0);
    int over2 = x < 0 && y > 0 && sum > 0;
    return over1 || over2;
}

3.乘法溢出判断

难度:5星(有最多10个符号限制)
思路:
已知满足x && mu/x == y (mu 是 x*y)就是不溢出,但是有一个特例就是0xffffffff, 0x80000000相乘时,是溢出的,但是判断会出错好像是会core dumped,直接报错(也是整数除法溢出的唯一一个情况)
所以需要用除法之外的其他判定条件来排除这种情况,也就是需要说清楚在这种情况下是溢出的;
这也导致不能从不溢出出发,而需要从什么情况会溢出出发来写代码。

/* 12
 * multiplyFlow - return whether the multiplication of two integer x and y overflows(two's complement number)
 * if overflow or underflow, return 1
 * Examples: multiplyFlow(0xffffffff, 0x80000000)=1, multiplyFlow(1, 2)=0
 * Legal Ops: * / ^ && || ! < > ==
 * Maxops: 10
 * Rating: 3
 */
//最开始版本,会出现报错floating point exception (core dumped)
int multiplyFlow(int x, int y){
    int mu = x*y;
    int re = (x && y && (mu/x != y ||  mu/y != x));
    return re;
}
 //改进版
int multiplyFlow(int x, int y){
    int mu = x*y;
    int re = (x < 0 && y < 0 && mu < 0) || (x && !(mu/x == y));// ||的先后顺序不能变,否则也会core dumped
    //因为是利用前面先排除了会dumped的情况,再进行除法
    return re;
}

//最终版:(后来减少一个符号)
int multiplyFlow(int x, int y){
    int mu = x*y;
    return ((x ^ y) > 0 && mu < 0) || (x && !(mu/x == y));
}

浮点数运算

1.计算2*f

难度:4星
思路:其实就是浮点数的运算,对各种情况分类处理然后进行合并就好。需要考虑到规格化数,非规格化数,0,NaN,Inf。
因为最开始对浮点数运算不够理解,参考了别的文章才写出来第一个,后来进行合并化简又参考了知乎专栏的文章

代码很清楚了,注释也挺详细,直接上代码:

/* 13
 * twoTimesFloat - 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: 3
 */
//最初版本:
unsigned twoTimesFloat(unsigned uf) {
   int S = uf & (1 << 31);//符号位
   int E = (uf >> 23) & 0xff;//阶码
   int mask = (1 << 23) - 1;//23个1
   int M = mask & uf;//尾数
   
   if((!E & (M == 0)) || (E == 0xff)) //0,NaN,无穷大的情况下返回自身
        return uf;
   if(!E) //非规格化数
   {
       M = (M << 1) & mask;
       if(1 & (M >> 22))//尾数最高位为1
           E += 1;
   }
   else//规格化数
   {
   		//判断阶码是否溢出
       if(E - 0xff == 0) //加1后阶码溢出,返回无穷大
       {
          M = 0;
          E = 0;
       }
       else
           E += 1;
   }
   E <<= 23;
   return S|E|M;
}

//改进/最终版:
unsigned twoTimesFloat(unsigned uf) {
    int E = (uf >> 23) & 0xFF;
    if (E == 0xFF)//NaN或Inf
      return uf;

    if (E == 0)//非规格化数或0
      return (uf << 1) | (uf & (1 << 31));
    
    return uf + (1 << 23); //正常计算,阶码+1,如果阶码溢出,返回Nan
}

2.将float转化为int

难度:4星
思路:主要分三种情况,超出范围的返回题目指定特殊值,不能表示的小数返回0,其余正常进行运算返回

具体看代码和注释:

/* 14
 * float2Int - 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 float2Int(unsigned uf) {
    int mask, EV, frac, i;
    mask = 1 << 31;//指定的特殊值,也有掩码作用,最后一步用于取符号
    EV = ((uf >> 23) & 0xFF) - 127;//计算出EV 阶(指数)

	//两种特殊情况
    if (EV > 31) //超出int表示范围,因为还有隐藏的1,所以是31不是32
      return mask;//返回指定的特殊值
    if (EV < 0)//小数无法用int表示,舍为0
      return 0;

    frac = (uf & 0x007fffff) | 0x00800000; //取出尾数,并且包括隐含的1
    i = (EV > 23) ? (frac << (EV - 23)) : (frac >> (23 - EV)); //求出int可以表示的整数值,即将阶的部分通过移位计算出来
    return (uf & mask) ? -i : i; //根据符号决定正负
}

3.求2.0的x次方

难度:3星
思路:按照阶的大小分类讨论,float中规格化数阶码最小为1(阶最小为-126),阶码最大为254,(阶最大为127);
所以阶小于等于0时返回0;阶大于等于255时,返回INF
其余情况为正常阶码,可以正常表示

具体代码如下:

/* 15
 * twoPowerInt - 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
 *   0. If too large, return +INF.
 * 
 *   Legal ops: Any integer/unsigned operations incl. ||, &&. Also if, while 
 *   Max ops: 30 
 *   Rating: 4
 */
unsigned twoPowerInt(int x) {
    int E = x + 127;
    if (E <= 0)//为0 或者难以表示的非规格化数,返回0
        return 0;
    if (E >= 0xFF)//过大无法表示或者为INF和NaN,都返回+INF
        return (((1 << 10) - 1 ) << 23 ) - (1 << 31);
    return E << 23;//正常阶码,正常表示
}

参考文章

指路本次lab和笔记参考过觉得很不错的文章:
参考文章1
参考文章2

如果本文不错记得点个赞哦!谢谢阅读!

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值