csapp | datalab中的howManyBits

题目

在这里插入图片描述
用有限的运算符(逻辑运算、移位运算等)实现功能:表示输入x所需要的最少bit位数。例:f(12)=5,f(-1)=1。

分析

可能有更好的方法,本方法仅为一种解决方案。
首先分为补码 正负分析,然后考虑边界情况。

一般情况分析

先用变量minus来记录补码的正负,int类型共有32位,如果第31位为0说明是非负,否则为负数。

非负情况:当补码为非负时,需要找出数中从大到小第一个为1的位置pos(0-31)。再添加上一位符号位即可。(由于索引范围为0-31,而个数范围为1-32,最后pos需要+1,最高位1在第pos位指的是共有(pos+1)bits)。

负数情况:当补码为负时,将x转换为相反数-x(tmin=-0x80000000有越界风险需要另外讨论),同样寻找相反数|x|中从大到小第一个为1的位置pos,观察例子f(-5)的绝对值2进制形式为101,3个bits但结果为4需要添加1;f(-1)绝对值2进制形式为1,1个bit结果为1不需要添加1。思考这是由于n位补码的表示范围为 [ − 2 n − 1 , 2 n − 1 − 1 ] [-2^{ n-1},2^{n-1}-1] [2n1,2n11],补码中负数与绝对值的无符号代数关系为:
x + ∣ x ∣ = 2 n x+|x|=2^n x+x=2n①当 ∣ x ∣ = 2 p o s |x|=2^{pos} x=2pos时,假设 n = p o s + 1 n=pos+1 n=pos+1,x刚好为能表达的最小数, x = 2 p o s x=2^{pos} x=2pos, x + ∣ x ∣ = 2 p o s + 1 x+|x|=2^{pos+1} x+x=2pos+1式子刚好满足;
②而当 ∣ x ∣ > 2 p o s |x|>2^{pos} x>2pos,根据补码的特性可知在x中在pos之前的位拥有一个符号位1,则假设 n = p o s + 1 n=pos+1 n=pos+1 x + ∣ x ∣ > 2 p o s + 1 = 2 n x+|x|>2^{pos+1}=2^{n} x+x>2pos+1=2n不成立,所以假设 n = p o s + 2 n=pos+2 n=pos+2,pos+1存在一个符号位, x + ∣ x ∣ = 2 n x+|x|=2^{n} x+x=2n,则该补码的表示范围在 [ − 2 p o s + 1 , 2 p o s + 1 − 1 ] [-2^{ pos+1},2^{pos+1}-1] [2pos+1,2pos+11] ∣ x ∣ < 2 p o s + 1 , x > − 2 p o s + 1 |x|<2^{pos+1},x>-2^{pos+1} x<2pos+1x>2pos+1在范围内,所以可以表示;
使用变量flag对①和②情况进行区分。

综上:

  1. 首先 x = ∣ x ∣ x=|x| x=x
  2. 得到最高位1的位置索引pos
  3. 获取flag
  4. 统计结果:ans=pos+!minus+minus&flag+1

求解pos和flag

pos:首先构造16位(pos1)mask1(0xffff0000)与x进行&运算获得tmp1,(tmp1=!tmp1使得当前16位拥有1时tmp1为0,否则为1)。然后根据tmp1,16±8决定下一个判断位置(pos2)是24还是8,然后构造前pos2位mask2与x进行&运算获得tmp2,以此类推,5次就可以获得pos的位置。(具体操作方式见技巧部分)

flag:在获得1最大位pos后,构造前后(pos-1)位的mask,flag=!!(mask & x),若pos之后还有1则flag=1,否则flag=0。

边界情况

①当x=0x80000000,取反加1为本身,所以得到pos=31,符合。
②当x=0,由于没有1,最后pos=1-1=0,当作为正数结果+1,以及原本转换+1,ans=2,有误需要特别处理;在判断flag时用到了(0-1),而移位操作右操作数不能为负数,需要处理。
③当x=-1,最后pos=1,与情况②遇到的问题相同。
综上,对于 p o s − 1 = − 1 pos-1=-1 pos1=1的情况,采取将 p o s + 1 pos+1 pos+1以及 x = x < < 1 x=x<<1 x=x<<1方式计算flag避开;而对于x=0情况添加bool变量zero记录x是否为0,如果为0最后pos-1即可。

技巧

补码取相反数

x = ~ x + 1

(其中x!=MIN 0x80000000)

MASK构造

(1 << pos) - 1可构造出形如[00000,…,1111]的掩码
按位取反可获得[1111,…,0000]的掩码

绝对值

前提:x!=MIN 0x80000000
minus = x >> 31 & 1 判断正负
x=(~ 0 + minux) & ( ~ x + 1) + x & (~ 0 + ! minux)

z=x+y*(±1)用逻辑运算计算

说明:假设根据bool值tmp判断+1或者-1 且可以使用+运算 不能使用-运算
①将 z = x + y ∗ ( + − 1 ) z=x+y*(+-1) z=x+y(+1)转变为问题 z = x − y + ( y + y ) ∗ ( 0 / 1 ) z=x-y+(y+y)*(0/1) z=xy+(y+y)(0/1)
x = x − y x=x-y x=xy =》 x = x + (     ˜ y + 1 ) x=x+(\~{~~~}y+1) x=x+(   ˜y+1) y = y + y y=y+y y=y+y
②使tmp值可以映射如下:

00x000000
10x11111111

tmp = ~ 0 + ! tmp
综上:转换为表达式

x = x + (~y + 1) & (~0 + tmp)

a ? x : y 用逻辑运算表达

x1 = !a
x2 = !!a
ans = (~0 + x1) & x + (~0 + x2) & y

代码

int howManyBits(int x) {
        int minus = (x >> 31) & 1;
        int mask1, mask2, mask3, mask4, mask5, mask6;
        int fac2, fac3, fac4;
        int tmp1, tmp2, tmp3, tmp4, tmp5;
        int pos1, pos2, pos3, pos4, pos5;
        int flag;
        int ans;
        int constant1, constant2;
        int zero = !x;
        constant1 = ~1 + 1; // -1
        constant2 = ~0; // 11111111111111
        x = ((constant2 + !minus) & (~x + 1)) + (x & (constant2 + minus));
        mask1 = ~((1 << 16) + constant1);
        fac2 = ~4 + 1;
        fac3 = ~2 + 1;
        fac4 = ~1 + 1;
        tmp1 = !(mask1 & x);
        pos1 = 8 + (16 & (constant2 + tmp1));
        mask2 = ~((1 << pos1) + constant1);
        tmp2 = !(mask2 & x);
        pos2 = (pos1 + fac2) + (8 & (constant2 + tmp2));
        mask3 = ~((1 << pos2) + constant1);
        tmp3 = !(mask3 & x);
        pos3 = (pos2 + fac3) + (4 & (constant2 + tmp3));
        mask4 = ~((1 << pos3) + constant1);
        tmp4 = !(mask4 & x);
        pos4 = (pos3 + fac4) + (2 & (constant2 + tmp4));
        mask5 = ~((1 << pos4) + constant1);
        tmp5 = !(mask5 & x);
        pos5 = (pos4 + fac4) + (1 & (constant2 + tmp5));
        pos5 += 1;
        x = x << 1;
        mask6 = ((1 << pos5) + constant1);
        flag = !!(mask6 & x);
        ans = pos5 + !minus + (minus & flag) + ((~0 + !zero) & constant1);
  return ans;
}

更好的代码

int howManyBits(int x) {
	int temp = x ^ (x << 1);
	int bit_16,bit_8,bit_4,bit_2,bit_1;
	bit_16 = !!(temp >> 16) << 4;
	temp = temp >> bit_16;
	bit_8 = !!(temp >> 8) << 3;
	temp = temp >> bit_8;
	bit_4 = !!(temp >> 4) << 2;
	temp = temp >> bit_4;
	bit_2 = !!(temp >> 2) << 1;
	temp = temp >> bit_2;
	bit_1 = !!(temp >> 1);
	return 1 + bit_1 + bit_2 + bit_4 + bit_8 + bit_16;
}

链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值