我试图找到一个数字的二进制基数,比如将数字舍入到它下面的最大整数的floor函数,我想将数字四舍五入到它下面的第一个二进制数。
例如:
for 1000 it should be 512
for 10 it should be 8
for 208 it should be 128
这是我尝试过的。我觉得日志功能会占用更多资源所以有没有更快的方法呢?
#include<stdio.h>
int main() {
unsigned long long int num;
unsigned long long int mask;
scanf("%llu", &num);
mask = 0x80000000;
while(mask >>= 1) {
if (mask & num)
break;
}
printf("%llu\n", mask);
return 0;
}
感谢:)
答案 0 :(得分:5)
int topBit(int n) {
while (true){
m = n & (n-1);
if (m == 0) return n;
n = m;
}
}
n & (n-1)
清除最底部的位置。只要这样做,直到你达到零,然后你知道前一个值只有一个位设置(输入中设置的最高值)。
答案 1 :(得分:2)
以二进制表示数字,然后查找最高有效位(最高非零位)。天真地你可以通过一次右移一位直到它为零来做到这一点 - 这是“一个太多”。这基本上就是你尝试过的方法。更快一点的是二分搜索。对于32位整数,向右移16;如果仍然> 0,右移8等。我相信你可以从这里弄明白。
代码示例:
typedef unsigned long long int ulli;
ulli floor2(ulli num){
int msb = 8*sizeof(num)/2;
ulli temp = ((ulli)1)<<msb;
while(msb>1){
msb/=2; // using divide for clarity
if(temp>num) temp>>=msb; else temp<<=msb;
}
if (temp>num) temp/=2;
return temp;
}
我针对topBit
以及builtIn
方法运行了此算法的一些基准测试。一个10M迭代的循环,生成一个“长”随机数,在我的系统上需要362 ms(没有编译器优化)。如果循环包含其中一种计算方法,则时间增加如下:
============= total net
builtin: 407 45
binary search: 579 215
topBit: 2295 1933
内置方法绝对是最快的 - 不是很令人惊讶!对于64位数字,topBit平均需要32个循环(设置一半的位,所以跳过)和二进制只有5个循环,所以你可以期待大约6倍的速度差异;这大致就是你所看到的。当我将ulli
定义为unsigned short
(16位)时,时差约为2倍。
答案 2 :(得分:2)
如果您正在使用GCC进行编译,则可以使用GCC builtins执行此操作。内置__builtin_clzll
计算无符号长long中前导零的数量。你可以用它来计算最重要位的位置,然后多次左移1来得到答案:
#include <limits.h>
然后使用:
unsigned long long result =
num ? 1LLU << (sizeof(unsigned long long)*CHAR_BIT - __builtin_clzll(num) - 1) : 0;
printf("%llu\n", result);
答案 3 :(得分:2)
This classic document有很多方法可以找到整数的下限(日志基数2)。找到日志后,您想要的数字当然是1&lt;&lt;日志中。
最引人入胜的建议就是这个
// Find the integer log base 2 of an integer with an 64-bit IEEE float
int v; // 32-bit integer to find the log base 2 of
int r; // result of log_2(v) goes here
union { unsigned int u[2]; double d; } t; // temp
t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] = 0x43300000;
t.u[__FLOAT_WORD_ORDER!=LITTLE_ENDIAN] = v;
t.d -= 4503599627370496.0;
r = (t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] >> 20) - 0x3FF;
上面的代码通过在尾数中存储整数来加载64位(IEEE-754浮点)double和32位整数(没有paddding位),而指数设置为252。减去minted double,252(表示为double),将得到的指数设置为输入值的对数基数v,剩下的就是将指数位移位到位(右边20位)并减去偏差,0x3FF(十进制1023)。这种技术只需要5次操作,但是许多CPU在操作双精度方面都很慢,并且必须适应架构的结束性。
所以你想要的最终结果是1 << r
。请注意,与撰写本文时相比,双打操作现在更快 。关于这段代码的最好的事情是它不包含分支,因此管道很好。你一定要试一试。我刚才没有时间尝试基准测试,但这很有意思。
我不能保证此代码符合C标准。
答案 4 :(得分:1)
我假设如果一个数字已经是2或0的幂,则应该不加改变地返回它。仅限正数。
int floor2(int n)
{
if ((n & (n-1)) == 0)
return n;
while (((n+1) & n) != 0)
{
n = n | (n+1);
}
return (n + 1) >> 1;
}
这里花哨的位数利用了这样一个事实,即从一个数字中减去1(即2的幂)将所有位设置在它下面,而在数字上加1将设置最底部的零位。
答案 5 :(得分:1)
这个问题与发现最重要的问题密切相关;因为在那之后它只是一点点转移:
在这里详细描述了MSB:
Find most significant bit (left-most) that is set in a bit array
然后你做这样的事情:
int foo = 1000;
int bar = ((msb(foo) << 1) - 1) >> 1;
if ( bar > foo ) bar = bar >> 1;
你有它。
如果您使用的是英特尔架构,可以在gcc中使用__builtin_clz(计算前导零)来获取msb;
或
这是一种非常有趣的方法来计算没有CPU支持的前导零
答案 6 :(得分:1)
简短而甜蜜......
(提问者的示例代码在值> = 0x80000000LLU时遇到问题,修复此处。)
这只需要在循环中进行1次比较,而不是2次。
unsigned long long int MSMask(unsigned long long int) {
if (num == 0) {
return 0;
}
else {
unsigned long long int mask = 0x8000000000000000LLU;
while (!(mask & num)) mask >>= 1;
return mask;
}
}
答案 7 :(得分:0)
这比您的版本快2倍:
uint64_t g(uint64_t num) {
uint64_t r = 1;
num = num >> 1;
while(num>r) {
r <<= 1;
}
return r;
}
答案 8 :(得分:0)
int test(int num) {
int res = (num==0?0:1);
while(num>1) {
num=num>>1;
res=res<<1;
}
return res;
}
在num不大时比下面快。
答案 9 :(得分:0)
建议的比特混乱技巧的最坏情况表现可以/应该通过逐渐or
几个比特来改善:
int s=1;
while ((n+1) & n) {
n|=n >> s;
s<<=1;
}
return (n+1) >> 1;
当所有最低有效位都为1时,此片段退出,只需要一些log2(log2(n))迭代。
答案 10 :(得分:0)
我知道递归通常不如迭代效率高,但我无法自拔 - 我喜欢一个很好的递归:
unsigned topBit(unsigned n) {
unsigned m = n & (n-1);
return m ? topBit(m) : n;
}
ADDENDUM :在使用优化进行编译时,实际时间显示比@ LaurenceGonsalves的迭代版本略快。