搜索
您的当前位置:首页正文

(整理)计算二进制数中1的个数

来源:好走旅游网
精品文档

第二章 基础

2.1 操作最右侧位

a.最右侧的1->0(0101 1000->0101 0000) x&(x-1),可用来判断无符号整数是否为2的幂;x&(x+1)可用来检测无符号数是否为pow(2,n)-1的形式

b.析出最右侧的1位(0101 1000->0000 1000) x&(-x);析出最右侧的0位(0101 0111->0000 1000) ~x&(x+1)

c.识别后缀0的掩码(0101 1000->0000 0111) ~x&(x-1) or ~(x|(-x)) or (x&-x)-1

d.识别最右侧1位和后缀0的掩码(0101 1000->0000 1111) x^(x-1) (^表示异或)

e.向右传播最右侧的1位(0101 1000->0101 1111) x|(x-1)

f.将最右侧连续的1位串修改成0位串(0101 1000->0100 0000) ((x|(x-1))+1) & x,可用来检查一个非负整数是否具有pow(2,j)-pow(2,k)的形式(j>=k>=0)

g.将上述具有对偶性公式的1和0, (x+1)和(x-1), ~(x+1)和(-x), &和|相替换,而x与~x不变,则可以得出新的公式

h.寻找一个和给定非负整数有相同数目的1位的下一个大数(将集合表示成数,元素为数位):

s <- x&(-x) x=01111 0000, s=00001 0000

精品文档

精品文档

r <- s+x r=10000 0000

t <- ((x^r)>>2)/s x^r=11111 0000, t=00000 0111

y <- r|t y=10000 0111

寻找一个和给定非负整数有相同数目的1位的下一个大数: x&(x-1) + 1

2.4 绝对值函数

先计算y <- x>>31,然后使用 abs: (x^y)-y or (x+y)^y or x-(x<<1 & y)

nabs: y-(x^y) or (y-x)^y or (x<<1 & y)-x

2.7 符号函数

sign(x) = -1(x<0), 0(x=0), 1(x>0), 使用比较谓词指令(x>0)-(x<0) or (x>=0)-(x<=0)(可推广到x与y比较), 或者(x>>31)|(-x>>31)

2.9 符号传递

ISIGN(x,y) = abs(x)(y>=0), -abs(x) (y<0)

= (abs(x)^t)-t (t=y>>31)

= (abs(x)+t)^t

精品文档

精品文档

2.14 循环移位(x为无符号整数)

左循环移位n个位:y=(x<>(32-n))

右循环移位n个位:y=(x>>n)|(x<<(32-n))

摘自rotl.c,向左移位

unsigned __cdecl _rotl (

unsigned val,

int shift

)

{

shift &= 0x1f;

val = (val>>(0x20 - shift)) | (val << shift);

return val;

}

精品文档

精品文档

unsigned __int64 __cdecl _rotl64 (

unsigned __int64 val,

int shift

)

{

shift &= 0x3f;

val = (val>>(0x40 - shift)) | (val << shift);

return val;

}

2.19 交换寄存器

x = x oper y, y = y oper x, x = x oper y; oper可为+、-、异或和异或的补(≡)

a.交换寄存器的相应字段:给定x和y以及掩码m,当第i为的掩码m(i)=1时,交换x和y的第i位内容,否则不变

第一种方法:x = x^y; y = y^(x&m); x = x^y 第二种方法:tmp =

精品文档

精品文档

(x&~m)|(y&m); y = (y&~m)|(x&m); x = tmp

第三种方法:x = x≡y; y = y≡(x|m); y = x≡y 第四种方法:tmp = (x^y)&m; x = x^t; y = y^t

b.同一寄存器的两个字段交换(x为非负整数)

x: |-----------------------------------| 假设C和D段占k位,m是对应于D段的各位值为1的掩码,现需要交换B和D段:

| A | B | C | D | E | t1 = [x^(x>>k)]&m; t2 = t1 << k; val = x^t1^t2

|-----------------------------------|

上述基于异或的交换方法,当m为0时退化为无操作,当m为全1时则是交换x和y的值

第三章 2的幂边界(非负整数)

3.1 上、下舍入到已知2的幂的倍数

假设舍入的因子以调整量的log2的形式给出,k=log2(调整量) (例如k=3,调整量=8)

下舍入到已知2的幂的倍数, x&((-1)<>k)<精品文档

精品文档

x&(-8) or (x>>3)<<3

上舍入到已知2的幂的倍数, t=(1<(x+7)&(-8) or x+(-x&7)

3.2 上、下舍入到下一个2的幂

下舍入到不大于x的最大的2的幂:

unsigned flp(unsigned x) { y = 0x80000000; do {

x = x | (x >> 1); while(y > x) y = x;

x = x | (x >> 2); y = y >> 1; x = x & (x-1);

x = x | (x >> 4); return; }while(x != 0);

x = x | (x >> 8); return y;

x = x | (x >> 16); 循环次数是前导0数目 循环次数是x中1位的数目

return x - (x >> 1);

精品文档

精品文档

}

上舍入到不小于x的最小的2的幂:

unsigned clp(unsigned x) {

x = x - 1;

x = x | (x >> 1);

x = x | (x >> 2);

x = x | (x >> 4);

x = x | (x >> 8);

x = x | (x >> 16);

return x + 1;

}

第五章 位计数

5.1 1位计数

精品文档

精品文档

1.二分法: x = (x & 0x55555555) + ((x >> 1) & 0x55555555)

x = (x & 0x33333333) + ((x >> 2) & 0x33333333)

x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F)

x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF)

x = (x & 0x0000FFFF) + ((x >> 16) & 0x0000FFFF)

另外利用 pop(x) = x - x/2 - x/4 -...-x/2^31 (x/2^31 = 0, x为无符号整数)

int pop2(unsigned int x)

{

x = x - ((x >> 1) & 0x55555555); // 相当于x = (x & 0x55555555) + ((x >> 1) & 0x55555555)

x = (x & 0x33333333) + ((x >> 2) & 0x33333333);

x = (x + (x >> 4)) & 0x0F0F0F0F; // 相当于(x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F),无进位

x = x + (x >> 8);

精品文档

精品文档

x = x + (x >> 16);

return x & 0x0000003F;

}

其它:

int pop3(unsigned int x) // ?

{

unsigned int n;

// 每三位计算1数目

n = (x >> 1) & 033333333333;

x = x - n;

n = (n >> 1) & 033333333333;

x = x - n;

精品文档

精品文档

x = (x + (x >> 3)) & 030707070707; // 相邻的3位字段相加,形成6位字段和

x = x%63; // 将各个6位字段相加

return x;

}

int pop4(unsigned int x) // ?

{

unsigned int n;

// 每四位计算1数目

n = (x >> 1) & 0x77777777;

x = x - n;

n = (x >> 1) & 0x77777777;

x = x - n;

n = (x >> 1) & 0x77777777;

精品文档

精品文档

x = x - n;

x = (x+ (x >> 4)) & 0x0F0F0F0F; // 四位和变成八位和

x = x*01010101; // 将四个字节相加,和放在高阶字节上

return x >> 24;

}

// 稀疏字的1位计数法

int pop(unsigned int x)

{

int n = 0;

while(x != 0)

{

++n;

x = x & (x-1);

精品文档

精品文档

}

return n;

}

// 查表法

int pop(unsigned int x)

{

static char table[256] = {0, 1, 1,......, 7, 7, 8};

return table[x & 0xFF] + table[(x >> 8) & 0xFF]

+table[(x >> 16) & 0xFF] + table[(x >> 24)];

}

5.2 字的奇偶性

1.计算1位的个数,然后由结果的最右侧位决定

2.由r <- ⊕(x >> i) (0<=i精品文档

精品文档

y = x ^ (x >> 1)

y = y ^ (y >> 2)

y = y ^ (y >> 4)

y = y ^ (y >> 8)

y = y ^ (y >> 16)

若将右移位变成左移位,则x的奇偶性出现在y的最左侧位,而y的第i位给出x从这一位到最右侧位奇偶性 3. x = x ^ (x >> 1)

x = (x ^ (x >> 2)) & 0x11111111

x = x * 0x11111111 // 将各个四位数加到一起并把和放入到高阶十六进制位的数字中

p = (x >> 28) & 1 // p=1(odd) or p=0(even)

5.3 前导0计数

int nlz1(unsigned int x) int nlz2(unsigned int x)

{ {

精品文档

精品文档

int n = 0; unsigned int y, n=32;

if(x == 0) return 32; y = x >> 16; if(y != 0) {n -= 16; x = y;}

y = x >> 8; if(y != 0) {n -= 8; x = y;}

if(x <= 0x0000FFFF) // if((x & 0xFFFF0000) == 0) y = x >> 4; if(y != 0) {n -= 4; x = y;}

{ y = x >> 2; if(y != 0) {n -= 2; x = y;}

n += 16; x = x << 16; y = x >> 1; if(y != 0) return (n-2);

} return (n-x);

if(x <= 0x00FFFFFF) // if((x & 0xFF000000) == 0) // n = 32; c = 16;

{ // do{

n += 8; x = x << 8; // y = x >> c; if(y != 0) {n -= c; x = y;}

} // c = c >> 1;

精品文档

精品文档

if(x <= 0x0FFFFFFF) // }while(c != 0);

{ // return (n-x);

n += 4; x = x << 4; }

} if(x <= 0x3FFFFFFF) { n += 2; x = x << 2; } if(x <= 0x7FFFFFFF) n += 1; } int nlz4(unsigned int x) 精品文档

int nlz3(int x)

{

int y=x, n= 0;

L: if(x < 0) return n;

if(y == 0) return (32-n);

n += 1;

x = x << 1;

y = y >> 1;

goto L:

return -1;

精品文档

{ }

x = x | (x >> 1);

x = x | (x >> 2);

x = x | (x >> 4);

x = x | (x >> 8);

x = x | (x >> 16);

return pop(~x); // 见5.1节

}

与对数关系:(无符号整数x!=0) floor(log2(x)) = 31 - nlz(x); ceiling(log2(x)) = 32 - nlz(x-1)

5.4 后缀0计数

1. ntz(x) = 32 - nlz(~x & (x-1)) = pop(~x & (x-1)) = 32 - pop(x | -x)

2. 二分法

int ntz(unsigned int x)

精品文档

精品文档

{

int n = 1;

if(x == 0) return 32;

if((x & 0x0000FFFF) == 0) {n += 16; x = x >> 16;}

if((x & 0x000000FF) == 0) {n += 8; x = x >> 8;}

if((x & 0x0000000F) == 0) {n += 4; x = x >> 4;}

if((x & 0x00000003) == 0) {n += 2; x = x >> 2;}

return n - (x&1);

}

第六章 字搜索

6.1 寻找第一个0字节

int zbytel(unsigned int x) // 最左0字节

{

精品文档

精品文档

if((x >> 24) == 0) return 0;

if((x & 0x00FF0000) == 0) return 1;

if((x & 0x0000FF00) == 0) return 2;

if((x & 0x000000FF) == 0) return 3;

els return 4; // 没有0字节

}

int zbytel(unsigned int x) // 将每个0字节变为0x80,每个非0字节变为0x00

{

unsigned int y = (x & 0x7F7F7F7F) + 0x7F7F7F7F;

y = ~(y | x | 0x7F7F7F7F); // leading 1 on zero bytes

int n = nlz(y) >> 3;

/* (1) 不用nlz指令的分支方法

if(y == 0) return 4;

精品文档

精品文档

else if(y > 0x0000FFFF)

return (y >> 31) ^ 1;

else

return (y >> 15) ^ 3;

return -1;

(2) 查表法

// 求对0x7F的余数,将原始值中最多的四个1位移动并压缩到最右侧4个位上

// 0x80808080%127=15; 0x80000000%127=8; 0x00008080%127=3 etc.

static char table[16] = {4, 3, 4, 4, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0};

return table[y % 127];

*/

return n;

}

精品文档

精品文档

该方法一种有趣变形:设a、b、c、d分别对应于谓词“第i字节非零”,则zbytel(x) = a + ab + abc + abcd

y = (x & 0x7F7F7F7F) + 0x7F7F7F7F;

y = y | x; // leading 1 on nonzero bytes

a = y >> 31;

b = (y >> 23) & a;

c = (y >> 15) & b;

d = (y >> 7) & c;

return a + b + c + d;

另外为了搜索一个字中第一个4位、随后12位或最后16位是否有0值,可以用0x77FF7FFF取代该方法中的掩码

int zbyter(unsigned int x) // 最右0字节

{

unsigned int y = (x & 0x7F7F7F7F) + 0x7F7F7F7F;

精品文档

精品文档

y = ~(y | x | 0x7F7F7F7F);

int n = ntz(y) >> 3;

// int n = (32 - nlz(~y & (y-1))) >> 3;

return n;

}

推广:

(1) 搜索等于任意给定值的字节:

对变量x和给定值进行异或,再搜索x中的0字节;例如为了搜索x中的ASCII空格(0x20),搜索x^0x20202020中的0字节; 同样为了搜索两个字x和y中相等字节的位置,可以搜索x^y中的0字节

(2) 搜索给定范围内的值( )

寻找0到9之间值得最左侧字节的下标:y = (x & 0x7F7F7F7F) + 0x76767676; y = ~(y | x | 0x7F7F7F7F); n = nlz(y) >> 3;

寻找字中第一个大写字母(0x41~0x5A):d = (x | 0x80808080) - 0x41414141; d = ~((x | 0x7F7F7F7F) ^ d);

精品文档

精品文档

y = (d & 0x7F7F7F7F) + 0x66666666; y = ~(y | d | 0x7F7F7F7F); n = nlz(y) >> 3;

6.2 寻找第一个给定长度或更长的1位串

int ffstr1(unsigned int x, int n)

{

int k, p=0;

while(x != 0) {

k = nlz(x);

x = x << k;

p += k;

k = nlz(~x);

if(k >= n)

return p;

x = x << k;

精品文档

精品文档

p += k;

}

return 32;

}

int ffstr2(unsigned nt x, int n)

{

int s;

while(n > 1)

{

s = n >> 1;

x = x & (x << s);

n = n - s;

}

精品文档

精品文档

return nlz(x);

}

例如计算一个 32位字中搜索长度>=8的连续1位串

x = x&(x << 1); x = x&(x << 2); x = x&(x << 4); // 顺序可以颠倒

n = nlz(x);

第7章 位与字节的重排列

7.1 位与字节的反转

if(k & 1) x = (x & 0x55555555) << 1 | (x & 0xAAAAAAAA) >> 1;

if(k & 2) x = (x & 0x33333333) << 2 | (x & 0xCCCCCCCC) >> 2;

if(k & 4) x = (x & 0x0F0F0F0F) << 4 | (x & 0xF0F0F0F0) >> 4;

if(k & 8) x = (x & 0x00FF00FF) << 8 | (x & 0xFF00FF00) >> 8;

if(k & 16)x = (x & 0x0000FFFF) << 16 | (x & 0xFFFF0000) >> 16;

k=31反转字中的位,例如:rev(0x01234567) = 0xE6A2C480

精品文档

精品文档

k=24反转字中的字节,例如:rev(0x1234567) = 0x67452301

k=16反转字中左右两个半字,例如:rev(0x1234567) = 0x45670123

k=7反转每个字节中的位,例如:rev(0x1234567) = 0x80C4A2E6

7.2 混洗位

abcd efgh ijkl mnop ABCD EFGH IJKL MNOP x=(x&0x0000FF00) << 8 | (x>>8) & 0x0000FF00 | x&0xFF0000FF

abcd efgh ABCD EFGH ijkl mnop IJKL MNOP x=(x&0x00F000F0) << 4 | (x>>4) & 0x00F000F0 | x&0xF00FF00F

abcd ABCD efgh EFGH ijkl IJKL mnop MNOP x=(x&0x0C0C0C0C) << 2 | (x>>2) & 0x0C0C0C0C | x&0xC3C3C3C3

abAB cdCD efEF ghGH ijIJ klKL mnMN opOP x=(x&0x22222222) << 1 | (x>>1) & 0x22222222 | x&0x99999999

aAbB cCdD eEfF gGhH iIjJ kKlL mMnN oOpP 外混洗结果

在上面序列前面加上x= (x>>16) | (x<<16)可得到AaBb CcDd EeFf GgHh IiJj KkLl MmNn OoPp 内混洗结果

以相反顺序可以实现操作的逆顺序

精品文档

精品文档

另外针对字左半部所有位都为0的情况,可以通过

x = ((x & 0xFF00) << 8) | (x & 0x00FF));

x = ((x << 4) | x) & 0x0F0F0F0F;

x = ((x << 2) | x) & 0x33333333;

x = ((x << 1) | x) & 0x55555555;

将0000 0000 0000 0000 abcd efgh ijkl mnop 变为 0a0b 0c0d 0e0f 0g0h 0i0j 0k0l 0m0n 0o0p

其逆过程为:

x = ((x >> 1) | x) & 0x33333333;

x = ((x >> 2) | x) & 0x0F0F0F0F;

x = ((x >> 4) | x) & 0x00FF00FF;

x = ((x >> 8) | x) & 0x0000FFFF;

7.3 转置位矩阵

a0 = 0123 b0 = 048c

精品文档

精品文档

a1 = 4567 ==> b1 = 159d

a2 = 89ab b2 = 26ae

a3 = cdef b3 = 37bf

简单方法:

b0 = (a0 & 8) | (a1 & 8) >> 1 | (a2 & 8) >> 2 | (a3 & 8) >> 3;

b1 = (a0 & 4) << 1 | (a1 & 4) | (a2 & 4) >> 1 | (a3 & 4) >> 2;

b2 = (a0 & 2) << 2 | (a1 & 2) << 1 | (a2 & 2) | (a3 & 2) >> 1;

b4 = (a0 & 1) << 3 | (a1 & 1) << 2 | (a2 & 1) << 1 | (a3 & 1);

一种好的编码:

m = m^(m<0x0000FFFF、0x00FF00FF、0x0F0F0F0F、0x33333333、0x55555555

void transpose32(unsigned int A[32]) { // 32*32矩阵转换

int j, k, m, t;

精品文档

精品文档

m = 0x0000FFFF;

for(j=16; j!=0; j=j>>1, m=m^(m<for(k=0; k<32; k=(k+j+1)&~j) {

t = (A[k] ^ (A[k+j] >> j)) & m;

A[k] = A[k]^t;

A[k+j] = A[k+j] ^ (t << j);

}

}

}

7.4 压缩或广义提取

例:掩码:0000 1111 0011 0011 1010 1010 0101 0101

字X: abcd efgh ijkl mnop qrst uvwx yzAB CDEF

结果:0000 0000 0000 0000 efgh klop qsuw zBDF

精品文档

精品文档

1、简单循环算法

unsigned int compress(unsigned int x, unsigned int m) {

unsigned int r=0, s=0, b;

do {

b = m & 1;

r = r | ((x & b) << s);

s += b;

x = x >> 1;

m = m >> 1;

}while(m != 0);

return r;

}

2、并行前缀方法

精品文档

精品文档

unsigned int compress(unsigned int x, unsigned int m) {

unsigned mk, mp, mv, t, i;

x = x & m; // clear irrelevant bits

mk = ~m << 1;

for(i = 0; i < 5; ++i) {

mp = mk ^ (mk << 1); // parallel prefix

mp = mp ^ (mp << 2);

mp = mp ^ (mp << 4);

mp = mp ^ (mp << 8);

mp = mp ^ (mp << 16);

mv = mp & m; // bits to move

m = m ^ mv | (mv >> (1 << i)); // compress m

t = x & mv;

精品文档

精品文档

x = x ^ t | (t >> (1 << i)); // compress x

mk = mk & ~mp;

}

return x;

}

编程之美2.1节中的扩展题第1题:如果变量是32位的Dword,则如何统计该二进制数中1的个数。

对于该题,我原本的想法还是想采用书中解法三,也就是用统计1中个数的算法v&(v-1),该算法时间复杂度为该32二进制数中“1”的个数。

后来,我参见了[1]中的解法,该解法甚妙,复杂度只有若干个位运算,与“1”的数目无关。由于[1]写的比较难懂,所以在这里解释一下他的解法。

解法一:

int Count(unsigned x)

{

x = x - ((x >> 1) & 0x55555555); // 1

精品文档

精品文档

x = (x & 0x33333333) + ((x >> 2) & 0x33333333); // 2

x = (x + (x >> 4)) & 0x0F0F0F0F; //3

x = x + (x >> 8); //4

x = x + (x >> 16); //5

return x & 0x0000003F; // 6

}

我们以0000 0000 0000 0000 0000 0000 1011 0101为例

第一行中,x>>1是右移一位,(x>>1) 为 0000 0000 0000 0000 0000 0000 0101 1010

该结果与0101 0101 0101 0101 0101 0101 0101 0101相与,等于 0000 0000 0000 0000 0000 0000 0101 0000

实际上,((x >> 1) & 0x55555555) 该步是想得到原数据x的偶数位的数据。(代码是先右移,再得奇数位,这个等价于:先得偶数位,再右移)

然后,x-((x >> 1) & 0x55555555) 则是 用原数据减去原数据的偶数位。结果是 0000 0000 0000 0000 0000 0000 0110 0101

精品文档

精品文档

其实,之所有要相减就是为了得到第一位与第二位1的个数的和,第三位与第四位1的个数的和,第五位与第六位1的个数的和,以此类推。

那么,为什么要用相减的方法来得到相邻两位的和呢?原理是:

相邻两位1的个数的和是:A-A/2 。原数据是A,右移相当于除2。

比如,如果原数据是1(01),那么一半就是0,相减后就是1,所以有1个“1”。如果原数据是3(11),那么一半就是1,相减后就是2,所有总共有2个“1”。

这样,经过第一行的运算,我们得到每两个相邻位的“1”的总和。

第二行,类似的,是求将原数据第一位第二位的“1“的个数的和 与 第三位第四位的“1”的个数的和 相加。这样,第二行执行后,我们可以得到每四位的“1”的个数的和

第三行,可以得到每八位的“1”的个数的和

第四行,可以得到每16位“1”的个数的和

第五行,可以得到32位数中所有1的个数的和。

第六行,由于32位的数据,1的个数最大也就32个,不可能超过2的6位方,所以只要取最后6位数据就是所有数据1的个数的和了。

这里用的是二分法,两两一组相加,之后四个四个一组相加,接着八个八个,最后就得到各位之和了。[1].

精品文档

精品文档

解法二:

int Count(unsigned x)

{

unsigned n;

n = (x >> 1) & 033333333333; // 1

x = x - n; // 2

n = (n >> 1) & 033333333333; // 3

x = x - n; // 4

x = (x + (x >> 3)) & 030707070707; //5

x = modu(x, 63); // 6

return x; //7

}

这个解法更妙。

精品文档

精品文档

需要注意的是,033333333333和033333333333都是指 八进制的数。

第一行第二行连起来看,这与解法一很类似,目的是为了得到第一位与第二位的“1”的个数和。注意,第31、32位中1的个数和在这一步中被统计好了。

第一行和第三行、第四行连起来看,目的是为了得到第一位与第三位的“1”的个数的和。然后,再与上步的结果加起来,就得到第一位、第二位、第三位的“1”的个数的和。

所以,从第一行到第四行就是为了得到 每三位一组的“1”的个数的和。原理是:

相邻三位的结果是:A-A/2-A/4. 算法中有两次向移。

比如,第一位第二位第三位是011, 则第一次移位后为01,相减后为10,再移位后为0,相减还是10,所以有2个“1”。

再比如,第一位第二位第三位是101,则第一次移位后为10,相减后为011,再移位后为1,相减后是010,所以有2个“1”。

第五行是求相邻六位的1的个数

第六行,比较难懂。在第五行执行完后,我们得到了七组数据,第32、31位为一组,第30-25为一组,……第6-第1为一组。所以可以写成

x_0+x_1*64 + x_2 * 64 * 64 + x_3 * 64 * 64* 64+ x_4 * 64 * 64* 64* 64+ x_5 * 64 * 64* 64* 64* 64+ x_6 * 64 * 64* 64* 64* 64* 64

精品文档

精品文档

这个数除以63的余数 肯定 与 x_0 +……+x_6 相等(因为32位的数据最多也就32个1)

[1]中的简短解释:首先是将二进制各位三个一组,求出每组中1的个数,然后相邻两组归并,得到六个一组的1的个数,最后很巧妙的用除63取余得到了结果。

这个程序只需要十条左右指令,而且不访存,速度很快。

精品文档

因篇幅问题不能全部显示,请点此查看更多更全内容

Top