技巧:求能被size整除的大于或等于num的最小值

来源:互联网 发布:fifa online3 mac版 编辑:程序博客网 时间:2024/06/16 07:15

最近看到CSDN上的一篇博文,提到了从驱动代码里发现的一个奇怪宏,如下:

#define E1000_ROUNDUP(num, size) ((num) = (((num) + (size) - 1) & ~((size) - 1)))

那篇文章的作者通过执行代码认为此代码的作用是求能被size整除的大于或等于num的最小值(size要为偶数),并且没有给出解释。

实际上,这段代码的作用确实是求能被size整除的大于或等于num的最小值。但size的条件要示比较高,要为2的幂才可以。
我们可以试想这个宏是怎么想出来的。设size是2的n次幂,num共有m+n个二进制位。要求能被size整除的大于或等于num的最小值,可以分为2部分来考虑:整除和进位。

首先是进位。如果num没有恰好被size整除的话,肯定要在后n位加上待定的值来使前m位进1。暂且不考虑后n位,只要让前m位加1,即加上2的n次方即可达到目的。但是如果num正好能被size整除,就不能进位了。因此可以加上2的n次方减1(即size-1)。

再次是整除,要被size整除,也就是要把num的后n位清零,同时保证前m位不变(因为进位的要求已经达到了)。C语言位操作中的“与”操作常用来完成位清零和位保持的工作。只要将num与后n位为0的数作与操作即可将后n位清0。其余的二进制位与1作与操作即可保持相应位的值不变。因此,我们只要构造一个后n位为0,其余各位为1的数即可。由于1的位数要根据num值的大小来确定,因此这个数不好构造。但后n位为1,其余各位为0的数很好构造,就是size-1。只要将size-1的各位按位取反就可得到我们想要的数。因此整除的操作可以通过“&~(size-1)”来完成。

综合起来,就是num=(num+size-1)&~(size-1)。将它变成宏的时候要注意将各参数都用括号括起来,防止宏展开的时候出现错误。因此就有了开篇的那句代码:

#define E1000_ROUNDUP(num, size) ((num) = (((num) + (size) - 1) & ~((size) - 1)))

从这个宏我们可以看出位操作的强大功能。与2的幂相关很多问题都可以用位参数来实现。如果不用位操作,本题可以用用如下代码来实现:

num=(num%size==0)?num:(num+size-num%size);

或者

num=(num%size==0)?num:(num/size+1)*size;

很多时候我们都要求能被size整除的大于或等于num的最小值。比如Linux下的DES加密函数,要求只能对8字节的整数倍的数据进行加密,如果不是,就要在数据的后面补零变成8字节的倍数再进行加密。8正好是2的幂,补0后的字节数就可以用上面的宏来求。同样,将一段数据写到操作系统的分页(4096字节)里也可以用上述的宏。