计算机系统实验datalab-handout中bitCount函数的具体实现及其原理分析

问题:如何计算32位整数中’1‘的个数。

算法理论

思路分析,当我们计算多项式的各个系数的权重和时,例如:

i = A 0 0 0*20 + A 1 1 1*21 + A 2 2 2*22 …A n n n*2n

实际上就是把各个位消权,最后得出

i = A 0 0 0 + A 1 1 1 + A 2 2 2 + A 3 3 3 + … A n n n

而实现的原理就是:对任何n的N次幂,用n-1取模得数为1;证明如下:

设:n(k-1) % (n-1) = 1 成立
则: nk % (n-1) = ( (n-1)*n(k-1) + n(k-1) )%(n-1) = 0 + n(k-1)%(n-1) = 1 也成立
又:n(1-1) % (n-1) = 1 成立
所以,对任意非负整数N,n(k-1) % (n-1) = 1 成立。

因此,对于一个系数为{A i i i}的以n为底的多项式P(N),P(N)%(n-1) = {sumA{i}}%(n-1);如果能保证sum{A{i}} < n-1,则只要用n-1对多项式进行取模即可完成消权,得到系数和。

问题探讨

所以问题就转换为:以2为底的多项式转换为以n为底的多项式,其中只要n足够大,使得n-1 > sum{A{i}成立即可。32位整数中sum{A{i}}位1或者0,其和为32。所以n-1 > 32 ==> n > 33,在二进制中 26=64 > 33,所以我们取6位二进制即可,如下:

i = t 0 0 0*640 + t 1 1 1*641 + t 2 2 2*642 + …t n n n*64n

应用于此问题只需要取如下6个位

000001 (2进制)

然后将我们所需要求的二进制位移位后与其相与并相加。
例如2进制6位数a

a = 110010
(a>>5)&(000001) + (a>>4)&(000001) + (a>>3)&(000001) + (a>>2)&(000001) + (a>>1)&(000001) + a&(000001) = 3

即可求出一个6位,即模为64的数的二进制数中’1‘的个数,以此类推,对于32位数t,求其’1‘的位数的函数如下所示

int bitcount(unsigned int n)  
{  
    unsigned int tmp;  

    tmp = (n &010101010101)  
     +((n>>1)&010101010101)  
     +((n>>2)&010101010101)  
     +((n>>3)&010101010101)  
     +((n>>4)&010101010101)  
     +((n>>5)&010101010101);      //其中010101010101等为八进制

    return (tmp%63);  
}  

在上面函数中,求tmp的步骤中,应用的就是上面求6位二进制数a的方法,不过这里求出来的是

a 1 1 1-a 2 2 2-a 3 3 3-a 4 4 4-a 5 5 5-a 6 6 6

上面即64进制数中的第1-6位(中间的‘-’是连接符)这样每个a i i i(i=1,2,3,4,5,6)代表的就是64为底的多项式的系数,结合上面求出的[#算法理论],此时求和只需要对底数64取模,即tmp%63。

算法优化

但是我们注意到,MIT HAKMEM最终的算法要比上面的代码更加简单一些。在上面的计算中,6位数中最多只有6个’1’,也就是000110(二进制),只需要3位有效位即可表示。上面的式子实际上是以1位为单位提取出’1’的个数再相加求和求出6位中’1’的总个数的,所以用的是&(000001)。如果以3位为单位算出’1’的个数再进行相加的话,那么就完全可以先加后与。首先还是以6位进行分析,因为实际只使用了3位存储结果的和:

tmp = (a>>2)&(001001) + (a>>1)&(001001) + a&(001001)
(tmp + tmp>>3)&(000111)

优化代码如下:

int bitcount(unsigned int n)  
{  
    unsigned int tmp;  

    tmp = (n &011111111111)  
     +((n>>1)&011111111111)  
     +((n>>2)&011111111111);  

    tmp = (tmp + (tmp>>3)) &030707070707;  

    return (tmp%63);  
}  

这样的目的是减少计算量,可以看到运算符的数量明显减少了。在上面的式子中,主要解释第二条语句,因为和最初的算法一样,对a进行移位并取求和,但在这次求和中,6位二进制中的低三位和高三位是分开求得的,所以必须将其整合成一个完整的64进制数。使用此优化的代码如下:

int bitcount(unsigned int n)  
{  
    unsigned int tmp;  
	
    tmp = n  
        - ((n >> 1) & 033333333333)  
        - ((n >> 2) & 011111111111);      //其中011111111111等为8进制

    tmp = (tmp + (tmp >> 3)) & 030707070707 ;

    return (tmp%63);  
}  

因为11位8进制数为33位2进制数,比32位整型多出一位,因此第一个八进制数会把最高位舍去(7->3)以免超出int长度,即07 => 03。
这次被优化的是3位2进制数“组”内的计算。回到多项式,我们知道一个3位2进制数是4a+2b+c(1<<2 = 4 ; 1<<1 = 2),我们想要求的是a+b+c,n>>1的结果是2a+b,n>>2的结果是a。于是可以得出如下多项式:

(4a+2b+c) - (2a+b) - (a) = a + b + c

上面多项式即是代码中实现的原理。

计算机系统实验datalab-handout中的具体实现

前面一系列的理论推导证明了我们可以得到32位整数中’1’的个数,但在具体完成bitCount实验时发现了一个问题,即上面的代码不能在32位数为负数时生效。且在实验环境中,不能直接用unsigned进行强制类型转换。

  1. 原理探讨
    在上面的所有代码示例中,变量定义为unsigned int类型,即永远为正数,所以代码能正确运行。但是,如果测试用例为负数时,会出现什么问题呢?
    这里我们就必须了解C语言中的算术移位了,算术右移是指右移时是最高位补符号位,即为负数时,非符号的最高位补符号位:

例如对一个8位数:10001000>>2
其结果为:11100010;而非00100010

上面的算术右移操作,会增加’1’的数量,因为右移过程中最高位会补‘1’。但如果我们能将算术位移改为逻辑位移,即最高位补0。那么上面的式子还是成立的。逻辑位移实现代码比较简单,就不解释了,代码如下:

int logicalshift(int x, int n)
{
	int y = ((~0)>>(32+~n))<<1;  //其中 ~n+1 = -1
	return (x>>n)&(~y);
}
  1. 具体代码实现
    上面代码中x为32位整数,n为移动的位数。
    了解了逻辑补0操作,上面代码可以改为如下形式(可在实验环境中直接运行):
/*
 * 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 y,n,m,k;
	k = ~((~0)<<30);    //定义操作数,减少操作符数量
	n = (0x49<<24) | (0x24<<16) | (0x92<<8) | 0x49;  //八进制011111111111
	m = (0xC7<<24) | (0x1C<<16) | (0x71<<8) | 0xC7;  //八进制030707070707
	y = (x&n) + ((x>>1)&n) + ( (x>>2) & (k) &n);     
	y = (y + ( (y>>3)&(k>>1) )) & m;
	return (y + (y>>6) + (y>>12) + (y>>18) + (y>>24) + ((y>>30)&0x03))&0x3f; //取余操作等价于 y%63
} 

为了通过实验测试,代码必须满足许多要求,其中主要是合法操作符类型(例如%用不了)、操作符数量和数据类型等等。但其原理和上面的第二个例子一样,可结合上面第二个代码例子理解。
总结:反正上面的一系列限制导致其代码实现过程非常令人头疼,但其实只要抓住了算术右移这一问题点,将可能导致错误的算术位移点改为逻辑右移,然后再适当减少操作符数量,就可以通过测试

参考文献:
MIT HAKMEM算法分析

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值