unsigned char reverse8( unsigned char c )
{
c = ( c & 0x55 ) << 1 | ( c & 0xAA ) >> 1;
c = ( c & 0x33 ) << 2 | ( c & 0xCC ) >> 2;
c = ( c & 0x0F ) << 4 | ( c & 0xF0 ) >> 4;
return c;
}算法
unsigned long func(unsigned long x)
{
x = (x & 0x55555555UL) + ((x >> 1) & 0x55555555UL);
x = (x & 0x33333333UL) + ((x >> 2) & 0x33333333UL);
x = (x & 0x0f0f0f0fUL) + ((x >> 4) & 0x0f0f0f0fUL);
x = (x & 0x00ff00ffUL) + ((x >> 8) & 0x00ff00ffUL);
x = (x & 0x0000ffffUL) + ((x >> 16) & 0x0000ffffUL);
return x;
}函数
x&=x-1;.net
本帖转自http://blog.csdn.net/todototry/archive/2007/04/23/1575900.aspxblog
可是更加详细的说明以下:get
这两个函数极非常巧妙,做了并行计算。数学
先看问题1: 反转一个字节。
它的算法是这样的: 首先是2位2位为一组,交换前一半和后一半。再4位4位为一组,交换前一半和后一半。再8位为一组,交换前一半和后一半it
。
可能还有点说不清楚。我举个例子。
将1 2 3 4 5 6 7 8 反转。
(1)2个2个为一组,交换前一半和后一半, 变成。
2 1 4 3 6 5 8 7
(2)4个4个为一组,交换前一半和后一半, 变成
4 3 2 1 8 7 6 5
(3)再8个为一组,交换前一半和后一半, 变成
8 7 6 5 4 3 2 1
反转成功。
这样的算法原本非常简单,很容易用数学概括法证实其正确。这函数, 巧妙就巧妙在做了并行计算,分组,它一次就计算完了。原理
先看第一个语句。c = ( c & 0x55) << 1 | ( c & 0xAA ) >> 1;
0x55其实就是01010101, 0xAA就是10101010
假设 c=abcdefgh
c & 0x55 = 0b0d0f0h, c & 0xAA = a0c0e0g0
跟着,前者左移一位, b0d0f0h0, 后者右移一位, 0a0c0e0g, 再一个|运算,就两位两位交换了位置。
想象一下,你有一个长纸条,分红一格一格,每格写一个字,假如你将纸条每隔一格剪一个小洞,滑一格,覆盖在原来的纸条上,你就会看到两个两个字交换了位置。
(注: |运算能够换成+运算,想想为何)二进制
第二个语句。 c = ( c & 0x33 ) << 2 | ( c & 0xCC ) >> 2;
0x33 = 00110011, 0xCC=11001100。并行
第三个语句。c = ( c & 0x0F ) << 4 | ( c & 0xF0 ) >> 4;
0x0f = 00001111, 0xF0=11110000.
这两个语句的做用也是 分组,将一半位变成0,移位滑动,跟着再组合,就分组交换了位置。
不防想象两个小纸条剪洞叠加。
这方法应该能够推广。
理解了问题1,也就很容易理解问题2了.
问题2: 判断32位整数二进制中1的个数。
和问题1同样,也是采用了分组并行计算。
基本方法是: 2位2位为一组,相加,看看有几个1。再4位4位为一组,相加,看看有几个1......
仍是说的不太明白。接着分析。
为了简单说明,先看看8位的情形。相应地,函数里面的语句变成。
x = (x & 0x55) + ((x >> 1) & 0x55); (1)
x = (x & 0x33) + ((x >> 2) & 0x33); (2)
x = (x & 0x0f) + ((x >> 4) & 0x0f); (3)
return x;
假设x=abcdefgh. 0x55=01010101
x & 0x55 = 0b0d0f0h. (x>>1) & 0x55 = 0a0c0e0g。相加。就能够知道2位2位一组1的个数。
好比x=11111111
x= (x & 0x55) + ((x >> 1) & 0x55); 以后x=10101010。你2位2位地看,10=2, 就是2 2 2 2, 就是说各组都是2个1。
好比x=00101001
x= (x & 0x55) + ((x >> 1) & 0x55); 以后x=00010101。你2位2位地看,就是0 1 1 1, 前1组只有0个1,后面的组都是1个1。
好啦。再来看。0x33=00110011。
x=abcdefgh.
x=(x & 0x33)+((x >> 2)&0x33); 至关于, 00ab00ef + 00cd00gh。
由于语句(1)以后。ab指示了头两位有多少个1,cd指示了下两位有多少个1。相加00ab+00cd就指示前4位有多少个1。这样就是4位4位为一组。注意这样的分组,组与组之间永远都不会产生进位的。正由于不会产生进位,才能够分开来看。
好啦。下面的过程都是同样的,再也不多说。
8位,16位,32位都同样。
反过来推导,基本思想仍是2分法。
给你一个x,设f(x,n)表示x中2进制位1的个数,则
f(x)=f(x & 0x0000ffffUL) + f((x >> 16) & 0x0000ffffUL)
一个32位的化成两个16位的,16位的再化成两个8位的,并且能够就在x上进行计算,保持数量不变,一直1位的x自己32位。
再从前面日后走
unsigned long func(unsigned long x)
{
第一步,把x当作32个数,每一个数就是一个bit,它自己在数量上就表示它们1的个数,
5的二进制是0101,就是01010101,运算完成后,它将成为16个数,相邻两个位的数联合表示原来1的个数,好比:
00 01 10 11 >>1= 00 00 11 01
& 01 01 01 01 & 01 01 01 01
= 00 01 00 01 = 00 00 01 01
+
= 00 01 01 10
=( 0 1 1 2)
完成以后,原数据将成为相邻2位组成一个数,共16个数(当作)。
x = (x & 0x55555555UL) + ((x >> 1) & 0x55555555UL);
第二步,把这16个数,又每相邻两个相加,这时候一个数是2位,因而相与的数就是 00110011,即0x33333333UL,原理同上,运算结束后,结果又缩小为8个数,每一个数用4位表示,表示是对应4位的1的个数,好比:
( 0 1 1 2)
0001 0110 >>2= 0000 0101
& 0011 0011 & 0011 0011
= 0001 0010 0000 0001
+
= 0001 0011
=( 1 3)
后面同理。 x = (x & 0x33333333UL) + ((x >> 2) & 0x33333333UL); x = (x & 0x0f0f0f0fUL) + ((x >> 4) & 0x0f0f0f0fUL); x = (x & 0x00ff00ffUL) + ((x >> 8) & 0x00ff00ffUL); x = (x & 0x0000ffffUL) + ((x >> 16) & 0x0000ffffUL); return x; }