C 找到2的幂的优雅方法
我的问题很类似: 给出C 找到2的幂的优雅方法,c,math,bit-manipulation,C,Math,Bit Manipulation,我的问题很类似: 给出x=2^y作为输入,我想输出y。 不同之处在于,我在C中编码,而不是C++,我确信输入中只有一个比特集,所以我想知道是否有更有效的方法来解决这个问题。 以下是我的尝试: unsigned int get_power_of_two(unsigned int x) { unsigned int y=0; for(unsigned int input=x; input>0; input=input>>1) { if(inp
x=2^y
作为输入,我想输出y
。
不同之处在于,我在C中编码,而不是C++,我确信输入中只有一个比特集,所以我想知道是否有更有效的方法来解决这个问题。
以下是我的尝试:
unsigned int get_power_of_two(unsigned int x)
{
unsigned int y=0;
for(unsigned int input=x; input>0; input=input>>1)
{
if(input & 1U)
{
break;
}
y++;
}
return y;
}
与@Dave答案中建议的查找表相比,这种效率是多少?
(同样,我是用C编写代码的,所以我没有像
下限这样的迭代器函数)你的算法的效率是O(logx)
,而Dave的(对二的幂进行二进制搜索)是O(logx)
。所以他的速度逐渐加快
最快的方法,当然是使用<代码> BSF 指令。
< P>作为一个旁注,您应该考虑重命名函数<代码> GETYPOWIORY OF2B/<代码> <代码> GETYLogLog2(/Cord>)/<
如果经常调用此函数,则可以初始化一个相对较小的查找表
使用此表,您可以逐个字节检查每个输入编号,如下所示:
#include <limits.h>
static unsigned int table[1<<CHAR_BIT];
void init_table() // should be called once
{
for (unsigned int n=0; n<CHAR_BIT; n++)
table[1<<n] = n;
}
unsigned int get_log_two(unsigned int x)
{
for (unsigned int n=0; x>0; n+=CHAR_BIT, x>>=CHAR_BIT)
{
unsigned int y = x & ((1<<CHAR_BIT)-1);
if (y > 0)
return n+table[y];
}
return ~0; // will never be reached during runtime
}
#包括
静态无符号整数表[1除了其他人之前所说的方式,例如严格依赖于基础ISA的BSF或CLZ指令,还有一些其他方式,例如:
事实上,在这里你可以找到很多“小玩弄黑客”。这里有一个有趣的想法
如果您知道只有1位设置,那么为什么不使用开关呢
unsigned int get_log_two(unsigned int x)
{
switch(x)
{
case 1<<0: return 0;
case 1<<1: return 1;
case 1<<2: return 2;
case 1<<3: return 3;
case 1<<4: return 4;
case 1<<5: return 5;
case 1<<6: return 6;
case 1<<7: return 7;
case 1<<8: return 8;
case 1<<9: return 9;
case 1<<10: return 10;
case 1<<11: return 11;
case 1<<12: return 12;
case 1<<13: return 13;
case 1<<14: return 14;
case 1<<15: return 15;
}
return 0;
}
unsigned int get_log_two(unsigned int x)
{
开关(x)
{
案例1在您的案例中,因为您知道只设置了一个位,所以计算尾随零就足够了。这可以在没有硬件指令的情况下很快完成。检查一下,下面的代码就是从这里来的(我不是一个篡改完美的人……有时)
在您的情况下,因为v
只有一个位集,所以您不需要找到最低的位集;因此您可以跳过v&-v
。您的代码版本如下:
unsigned v; // this is the number with one bit set
unsigned r; // this becomes the exponent in v == pow(2, r)
static const unsigned MultiplyDeBruijnBitPosition[32] =
{
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
r = MultiplyDeBruijnBitPosition[(v * 0x077CB531U) >> 27];
查看链接了解更多信息,它又链接到它的源信息。我不知道BSF
/BSR
,但我想它不是在所有硬件上都可用的,对吗?它是一条i386指令。其他体系结构上也有类似的指令。TL;DR:除了C和C之外,所有体系结构都有前导零计数指令+ +从来没有标准化。它最近被提出,对于C++,它可以在2017的时候采用它。虽然可能比线性搜索更好,但是这里可能值得注意的是,当n被固定到你的int的大小时,它几乎不需要谈论渐近复杂性。任何解决方案都应该测试速度,比如分支P。预测和原始指令计数将在这里发挥作用。在Dave的回答中,二进制搜索很可能(但不一定)使用掩码进行二进制搜索时,如果所有位都设置为掩码的一半以上,则可以更快地进行二进制搜索,然后来回移动。当然,还有其他有效的方法不使用二进制搜索。与循环相比,它应该更快,因为在您的情况下,您不必增加迭代器。但我猜它仍然是一个O(logx)操作。这是一个常数时间方法,结果是“顺序1”:O(1)我认为这不是O(1)。在得到结果之前,你必须进行一些比较(取决于输入)。当然,这是O(1),因为案例的数量是恒定的;这只会进行16次比较,16次是常数,所以这是O(1).但一般来说,没有理由相信随着案例数量的增加,运行代码所需的时间保持不变!开关并不神奇。请这样想:假设您使用一种没有开关的语言编写此代码,因为编译器必须这样做。您将如何将其编写为O(1)随着案例数量的增加?感谢@EricLippert,我从来没有真正想过交换机在内部做什么。因此我查看了拆解,并做了一些基准测试。1)我看到交换机使用了重复的存储命令(“rep stosd”)因此,在所有标签中,标签元素增加所需的时间应该更长。2)将开关从16条语句扩展到32条语句所需的执行时间延长了约2%,这并不显著(出乎意料);但是,执行循环代码从16条语句到32条语句所需的时间增加了约40%(预计)再次感谢,这真的很有趣:-)有五种方法可以完成这里描述的任务:谢谢,我已经阅读了这篇bithack,因为上面已经给出了链接,但我一直在努力理解并适应我的具体情况:这似乎是高水平的优化!@Coconop,乘法比特hack有点像黑色杂志最简单的理解方法是:乘法为最高有效位的5位中的每一位设定值生成一个唯一值,当移位到最低有效位时,该值在[0,31]范围内.研究是为了找到这个神奇的因素,为什么这个数字恰好有这个属性并不重要,只是它有这个属性,而且它解释了为什么这些数字存在。然后,只需制作一个查找表,匹配映射到的两个幂中的任何一个。@Coconop,The(v&-v)消除的部分是将具有任何值的数字转换为只有一个位位于原始数字最低有效位集位置的1。通过2的补码求反,技巧等价于(v&(~v+1))。如果您考虑对二进制数执行这些操作,您就会明白为什么会这样。位集上方的部分是按位NOT,因此and“取消”它变为零,下面的部分都是1,直到1被加到它上面,在位集合的位置上滚动变成1,下面是0。@Coconop,这应该很快。这是三条指令(两个操作和一个内存读取)。所以你现在理解代码了吗
unsigned v; // this is the number with one bit set
unsigned r; // this becomes the exponent in v == pow(2, r)
static const unsigned MultiplyDeBruijnBitPosition[32] =
{
0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
};
r = MultiplyDeBruijnBitPosition[(v * 0x077CB531U) >> 27];