• 欢迎访问我的博客,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站
  • 本网站关闭了评论功能,联系请点击→邮箱
  • Ctrl+D 可快捷收藏本站点

不常见但是效率很高的算法

C/C++ gql 4年前 (2021-04-08) 924次浏览
int func(uint32 x){
    int countx = 0;      
    while( x )      
    {          
        countx ++;          
        x = x&(x-1);      
    }  
    return countx;  
} 

这是统计x的二进制数值中有多少个1的函数,(1000 )b – 1 = (0111)b,正好是原数取反。这就是原理。 用这种方法来求1的个数是很效率很高的。 不必去一个一个地移位。循环次数最少。
如0x0b的二进制为1011,循环第一次countx = 1;x = 1011 & 1010 = 1010;
循环第二次countx = 2;x = 1010 & 1001 = 1000;
循环第二次countx = 3;x = 1000 & 0111 = 0000;
跳出循环,这个时候countx为3,说明0x0b有三位bit为1

#define is_pow_tow(v)  ((v) && !((v) & ((v) - 1)))

用于判断是否是2的幂,原理是如果v是2的幂,v的二进制最高位(这里只代表可以存放下这个v的最小的位)必定是1,后面全是0,v-1的二进制最高位必定是0,后面全是1,所以(v & (v-1))必定为0.如果v不是2的幂,v-1的最高位必定不会改变,如1001,1001&1000,那么这个值必定不为0.

#define ALIGN(size, align)                 ((align + size - 1) & (~ (align - 1)))
#define ALIGN_4BYTE(size)                  ((4 + size - 1) & (~ (4 - 1)))
#define ALIGN_8BYTE(size)                  ((8 + size - 1) & (~ (8 - 1)))

该宏定义用于计算字节对齐,align为2的幂,size为实际大小。
如4字节对齐,现在我有个结构体,其大小为6字节,那么这个结构体需要的存储空间大小为ALIGN(6, 4);也可是使用ALIGN_4BYTE(6),((4 + size – 1) & (~ (4 – 1)))计算出来后为(1000)b ,共需要8个字节。假设这个需要的存储空间大小命名为addr,那么我们可以确定两点

  1. addr必定是align的倍数
  2. addr必定大于等于size,且不超过 align+size。
  3. align为2的n次幂

那么可以得到 size <= addr < (size + align),由于addr必定是align的倍数,且align为2的幂,那么align是一个整数,可以得到size <= addraddr <= (size + align – 1),我们最后想得到是addraddr = ?,这个时候已经很接近最后的答案了,我们要做的就是把addraddr <= 变为addraddr = ,那么如何去变呢?addr还得满足一个条件就是必定是align的倍数,那么addr % align是等于0的,所以也就是说两边同时除去align

size/align <= addraddr / align  <= ((size + align – 1) / align),这个变幻一下就得到(⌈⌉向上取整,⌊⌋向下取整)

⌈size/align⌉ = addraddr / align  = ⌊((size + align – 1) / align)⌋,那么addraddr  = ⌊((size + align – 1) / align)⌋ * align;那么我们就得到了addraddr,那么怎么用程序写出来呢,我们知道一个数除以2,实际就是右移一位,乘以2就是左移一位,这样不仅方便计算而且大大加快了运算速度,但是右移的时候就会造成最低位的丢失,这就是为啥C语言中做除法都是向下取整的原因。回到主题, ⌊((size + align – 1) / align)⌋ * align,其实就是右移n(2的n次幂)位后,再左移n位,其实最后对(size + align – 1)的影响就是丢失最低的n位bit。 001~1~1,一共n个bit的1,那么001~1~1 + 1  = 001~0~0,后面n个0,也就是2的n次幂,这就是align。最后可以得到((align + size – 1) & (~ (align – 1)))。

我们再次跟着这个思路计算一下ALIGN(6, 4);可以知道

  1. addr必定是4的倍数
  2. addr必定大于等于6,且不超过10。
  3. align为2的2次幂

可以得到 6 <= addr <= 9,两边除以4,⌈1.5⌉ = addraddr / align  = ⌊2.25⌋,也就是2 = addraddr / align = 2;也就是addraddr  = 2 *  align = 2 * 4 = 8;其中addraddr  = ⌊((size + align – 1) / align)⌋ * align就是9(1001)右移2位得到1(0010),左移2位得到8(1000),也就是丢掉了最低的2位.

再比如ALIGN(15, 8);就是15+8-1=23(0001 0111)右移3位(0000 0010),在左移三位(0001 0000),显示就是丢掉了最低的3位,也就是ALIGN(15, 8) = 16;


如未注明 , 均为原创。转载请注明原文链接:不常见但是效率很高的算法
喜欢 (0)