整理一些今天看到的比较神奇的算法,可以拓宽下自己的思维和视野。
一、一个Float类型的绝对值
一个数值的绝对值可以通过与符号位的按位与操作来实现,对于IA32 32bit处理器,符号位是0x80000000,对于IA32 64bit来说,符号位是0x8000000000000000。下面代码为double类型的取绝对值操作。
double x;
/* make x = abs(x) */
*(((int *) &x) + 1) &= 0x7fffffff;
二、指针对齐
指针的向上和向下对齐的操作可通过以下代码实现,代码中,a向b对齐,且b是2的指数次方:
//向下对齐
(a & ~(b-1)); //~(b-1) == -b,所以也可以写成如下形式
(a & -b);
//向上对齐
((a + (b-1)) & -b)
如果在实际应用中,想要创建一个名为a的数据结构,其含有c字节的内存,且使其向b字节对齐(b是2的指数次方),则可通过以下代码实现:
a=((typeof(a))(((int)(((void *)malloc(c+(b-1)))+(b-1)))&-b))
三、整数的平均值
下面,看几个神奇的操作:
(x+y) = ((x&y)+(x|y)) = ((x^y)+2*(x&y));
//那么,(x+y)/2可通过下面的操作实现
(x&y)+((x^y)/2) = (x&y)+((x^y)>>1)
//上一行代码的有点是这样不会导致溢出
四、按位倒置
一个整数按位倒置可通过以下代码实现:
unsigned int
reverse(register unsigned int x)
{
x = (((x & 0xaaaaaaaa) >> 1) | ((x & 0x55555555) << 1));
x = (((x & 0xcccccccc) >> 2) | ((x & 0x33333333) << 2));
x = (((x & 0xf0f0f0f0) >> 4) | ((x & 0x0f0f0f0f) << 4));
x = (((x & 0xff00ff00) >> 8) | ((x & 0x00ff00ff) << 8));
return((x >> 16) | (x << 16));
}
//此函数也可重写为如下函数
unsigned int
reverse(register unsigned int x)
{
register unsigned int y = 0x55555555;
x = (((x >> 1) & y) | ((x & y) << 1));
y = 0x33333333;
x = (((x >> 2) & y) | ((x & y) << 2));
y = 0x0f0f0f0f;
x = (((x >> 4) & y) | ((x & y) << 4));
y = 0x00ff00ff;
x = (((x >> 8) & y) | ((x & y) << 8));
return((x >> 16) | (x << 16));
}
五、Float类型数据的比较
Float类型数据的比较通过如下编码实现:
#define FasI(f) (*((int *) &(f)))
#define FasUI(f) (*((unsigned int *) &(f)))
#define lt0(f) (FasUI(f) > 0x80000000U) //小于0
#define le0(f) (FasI(f) <= 0) //小于等于0
#define gt0(f) (FasI(f) > 0) //大于0
#define ge0(f) (FasUI(f) <= 0x80000000U) //大于等于0
六、通过一个指针域来实现双向链表
通常,双向链表中有两个指针,分别之前当前节点node的前驱指针prev和指向当前节点后继的指针next,如果每个链表节点中只能存储一个指针,如何实现双向链表呢?可通过将当前节点指针赋值为其前驱指针和后继指针的按位异或值(XOR)来实现。
不幸的是,在C语言中貌似没有对指针的异或定义。
七、除法取整
除法的向上和就近取证如下所示:
(a+b-1)/b; //向上取整
(a+(b/2))/b; //就近取整
八、Gray码转换
有很多种传统方法,比如第k个Gray码可通过K^(K>>1)来实现,下面代码是Gray码与无符号二进制数的转换:
unsigned int
g2b(unsigned int gray)
{
gray ^= (gray >> 16);
gray ^= (gray >> 8);
gray ^= (gray >> 4);
gray ^= (gray >> 2);
gray ^= (gray >> 1);
return(gray);
}
九、整数常量乘法
整数乘法可以进行简单的转换,如x*y,如果x==5(4+1),则可通过如下方式来计算x*y:
y2 = y + y;
y4 = y2 + y2;
result = y + y4;
另外,如果y是整数,则可通过移位来实现,代码如下:
y4 = (y << 2);
result = y + y4;
十、两个整数的最大值和最小值
两个整数的最大值和最小值的计算可通过如下方式实现,其中x和y是2的补码形式:
//x和y的最小值
x+(((y-x)>>(WORDBITS-1))&(y-x));
//x和y的最大值
x-(((x-y)>>(WORDBITS-1))&(x-y));
十一、整数的指数次方
可参加整数的常量乘法中的技巧来计算整数的指数次方,如果x==5(4+1),则通过下面方式来计算y的x次方:
y2 = y * y;
y4 = y2 * y2;
result = y * y4;
十二、整数选择
通过以下代码实现整数的选择:
//实现以下if和else语句,如
if (a<b)
x=c;
else
x=d;
//通过下面代码实现此if else语句
((((a-b) >> (WORDBITS-1)) & (c^d)) ^ d)
十三、判断一个数是否是2的幂次方
//如果x是2的幂次方,则此表达式为0,否则x不是2的幂次方
(x&(x-1));
十四、计算整数的二进制表示前面有几个零
unsigned int
lzc(register unsigned int x)
{
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
return(WORDBITS - ones(x));
}
十五、求一个输的最低有效bit
位
//求得x的最低有效bit
(x^(x&(x-1)));
十六、求一个整数的log2的值
//向下取整的版本
unsigned int
floor_log2(register unsigned int x)
{
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
#ifdef LOG0UNDEFINED
return(ones32(x) - 1);
#else
return(ones32(x >> 1));
#endif
}
//向上取整的版本,即需要判断x是否是2的幂次方
unsigned int
log2(register unsigned int x)
{
register int y = (x & (x - 1));
y |= -y;
y >>= (WORDBITS - 1);
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
#ifdef LOG0UNDEFINED
return(ones(x) - 1 - y);
#else
return(ones(x >> 1) - y);
#endif
}
十七、求大于一个整数的最小2的幂次方的数
//Next Largest Power of 2
unsigned int
nlpo2(register unsigned int x)
{
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
return(x+1);
}
十八、求一个整数的最高有效bit位
unsigned int
msb32(register unsigned int x)
{
x |= (x >> 1);
x |= (x >> 2);
x |= (x >> 4);
x |= (x >> 8);
x |= (x >> 16);
return(x & ~(x >> 1));
}
十九、多项式计算
一个多项式的表达式如x0+x1*x+x2*x*x+x3*x*x*x+...,其更加有效的计算可通过表达式x0+x*(x1+x*(x2+x*(x3+x*(...))))来替代。
二十、统计32位整数中1的个数
unsigned int
ones32(register unsigned int x)
{
/* 32-bit recursive reduction using SWAR...
but first step is mapping 2-bit values
into sum of 2 1-bit values in sneaky way
*/
x -= ((x >> 1) & 0x55555555);
x = (((x >> 2) & 0x33333333) + (x & 0x33333333));
x = (((x >> 4) + x) & 0x0f0f0f0f);
x += (x >> 8);
x += (x >> 16);
return(x & 0x0000003f);
}
二十一、不使用临时变量交换两个数的值
//通过异或实现
x ^= y; /* x' = (x^y) */
y ^= x; /* y' = (y^(x^y)) = x */
x ^= y; /* x' = (x^y)^x = y */
//通过加减法实现
x = x+y;
x = x-y;
y = x-y;
二十二、计算整数二进制表达中尾部零的个数
//Trailing Zero Count
unsigned int
tzc(register int x)
{
return(ones((x & -x) - 1));
}