用于找到大于或等于给定值的2的最小幂的算法

时间:2008-12-13 08:08:50

标签: c++ algorithm assembly

我需要找到大于或等于给定值的2的最小幂。到目前为止,我有这个:

int value = 3221; // 3221 is just an example, could be any number
int result = 1;

while (result < value) result <<= 1;

它工作正常,但感觉有点幼稚。这个问题有更好的算法吗?

EDIT。有一些很好的汇编程序建议,所以我将这些标记添加到问题中。

16 个答案:

答案 0 :(得分:62)

这是我的最爱。除了初始检查它是​​否无效(&lt; 0,你可以跳过,如果你知道你只传入&gt; = 0数字),它没有循环或条件,因此将胜过大多数其他方法。这类似于埃里克森的回答,但我认为我在开始时递减x并在结尾加1并不像他的回答那么尴尬(并且最后也避免了条件)。

/// Round up to next higher power of 2 (return x if it's already a power
/// of 2).
inline int
pow2roundup (int x)
{
    if (x < 0)
        return 0;
    --x;
    x |= x >> 1;
    x |= x >> 2;
    x |= x >> 4;
    x |= x >> 8;
    x |= x >> 16;
    return x+1;
}

答案 1 :(得分:19)

ceil(log2(value))

ilog2()可以在3个asm指令中计算,例如http://www.asterisk.org/doxygen/1.4/log2comp_8h-source.html

答案 2 :(得分:11)

本着Quake II的0x5f3759df和Bit Twiddling Hacks的IEEE版本的精神 - 这个解决方案达到了一个双重提取指数作为计算楼层的方法(lg2(n))。它比公认的解决方案快一点,比Bit Twiddling IEEE版本快得多,因为它避免了浮点数学。在编码时,它假设double是一个小型端机上的真正* 8 IEEE浮点数。

int nextPow2(int n) 
{ 
    if ( n <= 1 ) return n;
    double d = n-1; 
    return 1 << ((((int*)&d)[1]>>20)-1022); 
} 

编辑:在同事的帮助下添加优化的x86程序集版本。速度提升4%,但仍比bsr版慢50%(对于n = 1..2 ^ 31-2,我的笔记本电脑为6秒vs 4)。

int nextPow2(int n) 
{ 
    if ( n <= 1 ) return n;
    double d;
    n--;
    __asm {
      fild    n 
      mov     eax,4
      fstp    d 
      mov     ecx, dword ptr d[eax]
      sar     ecx,14h 
      rol     eax,cl 
  }
} 

答案 3 :(得分:9)

在英特尔硬件上,BSR指令接近你想要的 - 它找到了最重要的设置位。如果你需要更精确,那么你可以想知道其余的位是否正好为零。 我倾向于认为其他CPU会有像BSR这样的东西 - 这是你想要回答的问题,以规范数字。 如果您的数字超过32位,那么您将从最重要的DWORD进行扫描,以找到第一个设置了 ANY 位的DWORD。 Edsger Dijkstra可能会说上面的“算法”假设你的计算机使用二进制数字,而从他那种崇高的“算法”角度来看,你应该考虑一下图灵机器或其他东西 - 显然我是更务实的风格。

答案 4 :(得分:6)

你的实现并不天真,它实际上是逻辑上的,除了它是错误的 - 它返回一个负数,大于最大整数大小的1/2。

假设您可以将数字限制在0到2 ^ 30范围内(对于32位整数),它可以正常工作,并且比任何涉及对数的数学函数快得多。

无符号整数会更好地工作,但你最终会得到一个无限循环(对于大于2 ^ 31的数字),因为你不能用&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;操作

答案 5 :(得分:5)

pow(2,ceil(log2(value));

log2(value)= log(value)/ log(2);

答案 6 :(得分:4)

Bit Twiddling Hacks页面提供了对密切相关问题的可能解决方案(即向下舍入而非向上)的探索,其中许多方法明显快于天真方法,这是一个很好的资源。做你正在寻找的各种优化。最快的解决方案是使用具有256个条目的查找表,将总操作数减少到大约7,从平均62(通过类似的操作计数方法)的原始方法。使这些解决方案适应您的问题只需一次比较和增加。

答案 7 :(得分:4)

这是位移技术的模板版本。

template<typename T> T next_power2(T value)
{
    --value;
    for(size_t i = 1; i < sizeof(T) * CHAR_BIT; i*=2)
        value |= value >> i;
    return value+1;
}

由于循环只使用常量,因此它会被编译器弄平。 (我查了一下)这个功能也是未来的证明。

这是一个使用__builtin_clz的人。 (也是未来证明)

template<typename T> T next_power2(T value)
{
    return 1 << ((sizeof(T) * CHAR_BIT) - __builtin_clz(value-1));
}

答案 8 :(得分:3)

你并没有真正说出“更好的算法”是什么意思,但是你提出的那个非常清楚(如果有些缺陷),我会假设你是在追求一种更有效的算法。

Larry Gritz已经提供了可能是最有效的c / c ++算法而没有查找表的开销,并且在大多数情况下就足够了(对于类似的算法,请参阅http://www.hackersdelight.org)。

正如其他地方所提到的,现在大多数CPU都有机器指令来计算前导零的数量(或等效地返回ms设置位)但是它们的使用是不可移植的 - 在大多数情况下 - 不值得付出努力。

然而,大多数编译器都具有“内在”功能,允许使用机器指令,但以更便携的方式。

Microsoft C ++有_BitScanReverse(),gcc提供了__builtin_clz()来有效地完成大部分工作。

答案 9 :(得分:3)

递归模板版本如何生成编译常量:

template<uint32_t A, uint8_t B = 16>
struct Pow2RoundDown { enum{ value = Pow2RoundDown<(A | (A >> B)), B/2>::value }; };
template<uint32_t A>
struct Pow2RoundDown<A, 1> { enum{ value = (A | (A >> 1)) - ((A | (A >> 1)) >> 1) }; };

template<uint32_t A, uint8_t B = 16>
struct Pow2RoundUp { enum{ value = Pow2RoundUp<((B == 16 ? (A-1) : A) | ((B == 16 ? (A-1) : A) >> B)), B/2>::value }; };
template<uint32_t A >
struct Pow2RoundUp<A, 1> { enum{ value = ((A | (A >> 1)) + 1) }; };

可以像这样使用:

Pow2RoundDown<3221>::value, Pow2RoundUp<3221>::value

答案 10 :(得分:1)

下面的代码重复地删除最低位,直到数字为2的幂,然后将结果加倍,除非数字是2的幂,然后开始。它的优点是在与设置的位数成比例的时间内运行。不幸的是,它的缺点是几乎在所有情况下都需要比问题中的代码或装配建议更多的指令。我只是为了完整性而加入它。

int nextPow(int x) {
  int y = x
  while (x &= (x^(~x+1))) 
    y = x << 1;
  return y
}

答案 11 :(得分:1)

我知道这是downvote-bait,但如果数量足够小(如8位或16位),直接查找可能是最快的。

// fill in the table
unsigned short tab[65536];
unsigned short bit = tab[i];

首先执行高位字然后是低位,可以将其扩展到32位。

//
unsigned long bitHigh = ((unsigned long)tab[(unsigned short)(i >> 16)]) << 16;
unsigned long bitLow = 0;
if (bitHigh == 0){
    bitLow = tab[(unsigned short)(i & 0xffff)];
}
unsigned long answer = bitHigh | bitLow;

移位或方法可能并不好,但也许可以扩展到更大的字大小。

(实际上,这给出了最高的1位。你必须将它向左移动1以获得下一个更高的2的幂。)

答案 12 :(得分:1)

我的相同版本:

int pwr2Test(size_t x) {
    return (x & (x - 1))? 0 : 1; 
}

size_t pwr2Floor(size_t x) {
    // A lookup table for rounding up 4 bit numbers to
    // the nearest power of 2.
    static const unsigned char pwr2lut[] = {
        0x00, 0x01, 0x02, 0x02,     //  0,  1,  2,  3
        0x04, 0x04, 0x04, 0x04,     //  4,  5,  6,  7
        0x08, 0x08, 0x08, 0x08,     //  8,  9, 10, 11
        0x08, 0x08, 0x08, 0x08      // 12, 13, 14, 15
    };

    size_t pwr2 = 0;                // The return value
    unsigned int i = 0;             // The nybble interator

    for( i = 0; x != 0; ++i ) {     // Iterate through nybbles
        pwr2 = pwr2lut[x & 0x0f];   // rounding up to powers of 2.
        x >>= 4;                    // (i - 1) will contain the
    }                               // highest non-zero nybble index.

    i = i? (i - 1) : i;
    pwr2 <<= (i * 4);
    return pwr2; 
}

size_t pwr2Size(size_t x) {
    if( pwr2Test(x) ) { return x; }
    return pwr2Floor(x) * 2; 
 }

答案 13 :(得分:0)

我喜欢这种转变。

我会满足于

    int bufferPow = 1;
    while ( bufferPow<bufferSize && bufferPow>0) bufferPow <<= 1;

这样循环总是终止,而&amp;&amp;和被评估几乎从未。 而且我认为两行不值得函数调用。根据你的判断,你也可以长或短,并且它非常易读。 (如果bufferPow变为负数,希望您的主代码将快速退出。)

通常在算法开始时只计算一次2次幂,所以无论如何优化都会很愚蠢。但是,如果有人厌倦了速度竞赛,那么会感兴趣...使用上面的例子和255 256 257 .. 4195 4196 4197

答案 14 :(得分:0)

可以通过除以2的日志

将任意日志函数转换为日志库2
$ /usr/local/pypy-1.9/bin/pypy
Python 2.7.2 (341e1e3821ff, Jun 07 2012, 15:38:48)
[PyPy 1.9.0 with GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
And now for something completely different: ``<arigato> yes but there is not
much sense if I explain all about today's greatest idea if tomorrow it's
completely outdated''
>>>> import math
>>>> print math.log(65535)/math.log(2)
15.9999779861
>>>> print math.log(65536)/math.log(2)
16.0
>>>>

它当然不会100%精确,因为涉及浮点运算。

答案 15 :(得分:-1)

这种方法非常快(在我的2.66 GHz Intel Core 2 Duo 64位处理器上)。

#include <iostream>
int main(void) {
    int testinput,counter;
    std::cin >> testinput;
    while (testinput > 1) {
        testinput = testinput >> 1;
        counter++;
    }
    int finalnum = testinput << counter+1;
    printf("Is %i\n",finalnum);
    return 0;
}

我在3,6和65496上进行了测试,并给出了正确答案(4,8和65536)。

对不起,如果这看起来有点神秘,我在写作之前就受到了几个小时 Doom 的影响。 :)