找到2的力量的优雅方式

时间:2014-03-06 11:08:49

标签: c math bit-manipulation

我的问题与Fastest way of computing the power that a "power of 2" number used?非常相似:

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编码所以我没有像lower_bound这样的迭代器函数)

5 个答案:

答案 0 :(得分:2)

算法的效率为O(log x),而Dave(对2的幂进行二进制搜索)的效率为O(log log x)。所以他渐渐变速了。

当然,最快的方法是使用BSF指令。

答案 1 :(得分:2)

作为旁注,您应该考虑将函数get_power_of_two重命名为get_log_two

如果您经常调用此函数,则可以初始化一个相对较小的查找表。

使用此表,您可以逐字节检查每个输入编号,如下所示:

#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
}

就“纯学术复杂性”而言,这不一定是最有效的方法,因为函数get_log_two不执行二元搜索。

尽管如此,考虑到任何平台(通常为4)上sizeof(unsigned int)的价值相对较小,对于平均和最差情况,它几乎会产生相同的性能。< / p>

答案 2 :(得分:2)

除了先前其他人所说的方式,例如严格依赖于底层ISA的BSF或CLZ指令,还有其他一些方法,例如:

http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious

事实上,你可以在那里找到很多'有点笨拙的黑客'。

答案 3 :(得分:1)

这是一个有趣的想法......

如果您知道只设置了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;
}

将此扩展到31,你将有一个很好的功能。 这应该很快;但只有在设置了一个位时才会起作用。

答案 4 :(得分:1)

在你的情况下,因为你知道只设置了一个位,所以它足以计算尾随零。这可以在没有硬件指令的情况下快速完成。看看这个answer,这就是下面的代码来自的地方(我不是一个篡改完美......有时候)。

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[((unsigned)((v & -v) * 0x077CB531U)) >> 27];

在你的情况下,因为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];

有关详细信息,请参阅链接,然后链接到其源信息。