给定一个整数,我该如何使用位旋转找到下一个最大的2的幂?


72

如果我有一个整数n,我怎样才能找到的下一个号码k > n,使得k = 2^i,其中一些i的元件N由按位移动或逻辑。

示例:如果我有n = 123,我怎么能找到k = 128,它是2的幂,而不是124只能被2整除的值。这应该很简单,但这使我难以理解。


3
这将是一个很棒的面试问题。
Rick Minerich,2009年

7
@Rick:我希望你不是一个面试官,我现在把一个贫穷的申请人放在一个紧要关头;-)
AndreasT

1
可能是stackoverflow.com/questions/466204/…的副本,无论如何,在此也可能对此问题感兴趣。
Yann Droneaud 2013年

如果n = 128,是否要找到k = 128或k = 256?
endlith 2013年

Answers:


95

对于32位整数,这是一条简单明了的路由:

unsigned int n;

n--;
n |= n >> 1;   // Divide by 2^k for consecutive doublings of k up to 32,
n |= n >> 2;   // and then or the results.
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
n++;           // The result is a number of 1 bits equal to the number
               // of bits in the original number, plus 1. That's the
               // next highest power of 2.

这是一个更具体的例子。让我们以二进制数221(即11011101)为例:

n--;           // 1101 1101 --> 1101 1100
n |= n >> 1;   // 1101 1100 | 0110 1110 = 1111 1110
n |= n >> 2;   // 1111 1110 | 0011 1111 = 1111 1111
n |= n >> 4;   // ...
n |= n >> 8;
n |= n >> 16;  // 1111 1111 | 1111 1111 = 1111 1111
n++;           // 1111 1111 --> 1 0000 0000

第九位有一个位,代表2 ^ 8或256,实际上是2的第二大幂。每个移位都将数字中所有现有的1位与某些先前未触及的零重叠,最终产生等于原始数字中位数的1位数。该值加1会产生新的2的幂。

另一个例子; 我们将使用131,即二进制文件10000011:

n--;           // 1000 0011 --> 1000 0010
n |= n >> 1;   // 1000 0010 | 0100 0001 = 1100 0011
n |= n >> 2;   // 1100 0011 | 0011 0000 = 1111 0011
n |= n >> 4;   // 1111 0011 | 0000 1111 = 1111 1111
n |= n >> 8;   // ... (At this point all bits are 1, so further bitwise-or
n |= n >> 16;  //      operations produce no effect.)
n++;           // 1111 1111 --> 1 0000 0000

实际上,256是131的第二高幂数。

如果用于表示整数的位数本身是2的幂,则可以继续无限制地有效扩展此技术(例如,n >> 32为64位整数添加一行)。


1
@AndreasT:没问题!(顺便说一句,要了解为什么为什么要一直上升到n >> 16,请考虑如果n已经是2的幂,会发生什么。 ,这就是为什么需要所有转移的原因。)
John Feminella 09年

2
+1是的,很好的技巧,我很喜欢:)在这里可以找到更多的
旋转

1
虽然这可行,但是任何人都可以解释一下为什么可行吗?
Divick

1
@endolith需要对数N移位。这需要log(log N)个移位,因此使用的移位较少。
约翰·费米内拉

1
k >= n不是k > n
jwalker

30

实际上有一个汇编解决方案(自80386指令集起)。

您可以使用BSR(反向位扫描)指令扫描整数中的最高有效位。

bsr扫描双字操作数或第二个字中从最高有效位开始的位。如果这些位全为零,则清除ZF。否则,将设置ZF,并在反向扫描时将找到的第一个设置位的位索引加载到目标寄存器中

(摘自:http : //dlc.sun.com/pdf/802-1948/802-1948.pdf

并且比用1增加结果。

所以:

bsr ecx, eax  //eax = number
jz  @zero
mov eax, 2    // result set the second bit (instead of a inc ecx)
shl eax, ecx  // and move it ecx times to the left
ret           // result is in eax

@zero:
xor eax, eax
ret

在较新的CPU中,您可以使用快得多的lzcnt指令(aka rep bsr)。lzcnt在一个周期内完成工作。


如果您的输入已经是2的幂,那么它不会返回它
Endolith

21

一种更数学的方式,没有循环:

public static int ByLogs(int n)
{
    double y = Math.Floor(Math.Log(n, 2));

    return (int)Math.Pow(2, y + 1);
}

2
谢谢。但是我正在寻找一种“位旋转”的方式。;-)
AndreasT 2009年

5
+1,不是OP想要的,但奇怪的是,我需要一个适合单个内联表达式的答案:2 ^ (floor(log(x) / log(2)) + 1)
zildjohn01 2010年

3
它为您带来了现有的价值。下一部分,y + 1为您提供第二大功率。
DanDan 2013年

1
@DanDan:但是,如果n已经是2的幂,它将给出下一个2的幂,而不是返回n
endolith 2013年

2
@endolith:如果n是2的幂,并且想要获取n而不是“ 2的下一个幂”,则可以使用2 ^ ( ceil(log(x) / log(2)) )。但这不是问题(……如何找到下一个数字k > n……)
Wauzl

12

这是一个逻辑答案:

function getK(int n)
{
  int k = 1;
  while (k < n)
    k *= 2;
  return k;
}

哈,真简单!没想到那个:)
v01pe

8

这是约翰·费米内拉(John Feminella)的答案,实现为循环,因此它可以处理Python的长整数

def next_power_of_2(n):
    """
    Return next power of 2 greater than or equal to n
    """
    n -= 1 # greater than OR EQUAL TO n
    shift = 1
    while (n+1) & n: # n+1 is not a power of 2 yet
        n |= n >> shift
        shift <<= 1
    return n + 1

如果n已经是2的幂,它也会更快返回。

对于Python> 2.7,对于大多数N来说,这更简单,更快:

def next_power_of_2(n):
    """
    Return next power of 2 greater than or equal to n
    """
    return 2**(n-1).bit_length()

enter image description here


2
如果您要使用移位,最好也shift <<= 1不要使用shift *= 2。不知道在Python中这是否真的更快,但是应该。
克里斯·米德尔顿

4

大于/大于或等于

以下摘录适用于下一个数字k> n,以使k = 2 ^ i
(n = 123 => k = 128,n = 128 => k = 256),由OP指定。

如果您希望2最小乘方大于OR等于n,则只需在上述摘要中替换__builtin_clzll(n)为即可__builtin_clzll(n-1)

使用GCC或Clang的C ++ 11(64位)

constexpr uint64_t nextPowerOfTwo64 (uint64_t n)
{
    return 1ULL << (sizeof(uint64_t) * 8 - __builtin_clzll(n));
}

使用martinecCHAR_BIT提出的增强功能

#include <cstdint>

constexpr uint64_t nextPowerOfTwo64 (uint64_t n)
{
    return 1ULL << (sizeof(uint64_t) * CHAR_BIT - __builtin_clzll(n));
}

使用GCC或Clang的C ++ 17(8到128位)

#include <cstdint>

template <typename T>
constexpr T nextPowerOfTwo64 (T n)
{
   T clz = 0;
   if constexpr (sizeof(T) <= 32)
      clz = __builtin_clzl(n); // unsigned long
   else if (sizeof(T) <= 64)
      clz = __builtin_clzll(n); // unsigned long long
   else { // See https://stackoverflow.com/a/40528716
      uint64_t hi = n >> 64;
      uint64_t lo = (hi == 0) ? n : -1ULL;
      clz = _lzcnt_u64(hi) + _lzcnt_u64(lo);
   }
   return T{1} << (CHAR_BIT * sizeof(T) - clz);
}

其他编译器

如果您使用的不是GCC或Clang编译器,请访问Wikipedia页面,列出Count Leading Zeroes按位函数

  • Visual C ++ 2005 =>替换__builtin_clzl()_BitScanForward()
  • Visual C ++ 2008 =>替换__builtin_clzl()__lzcnt()
  • icc =>替换__builtin_clzl()_bit_scan_forward
  • GHC(Haskell)=>替换__builtin_clzl()countLeadingZeros()

欢迎捐款

请在评论中提出改进建议。还建议您使用的编译器或编程语言的替代方案...

另请参阅类似答案


3

这是一个没有循环但使用中间浮点数的百搭模型。

//  compute k = nextpowerof2(n)

if (n > 1) 
{
  float f = (float) n;
  unsigned int const t = 1U << ((*(unsigned int *)&f >> 23) - 0x7f);
  k = t << (t < n);
}
else k = 1;

您可以在此处找到漏洞以及许多其他令人费解的骇客,包括John Feminella提交的。


1
wtf!8-),将检查出来。Thx
AndreasT


2

如果您使用GCC,MinGW或Clang:

template <typename T>
T nextPow2(T in)
{
  return (in & (T)(in - 1)) ? (1U << (sizeof(T) * 8 - __builtin_clz(in))) : in;
}

如果使用Microsoft Visual C ++,请使用function_BitScanForward()代替__builtin_clz()


1
参见另一个问题中的类似答案:stackoverflow.com/a/10143264/938111(由ydroneaud于2012年回答)。有关其他编译器,请参见Wikipedia内置的按位函数:en.wikipedia.org/wiki/Find_first_set#Tool_and_library_support             此C ++ 64位版本:        constexpr uint64_t nextPowerOfTwo64 (uint64_t x) { return 1ULL << (sizeof(uint64_t) * 8 - __builtin_clzll(x)); }
oHo 2015年


1

有点纠结,你说?

long int pow_2_ceil(long int t) {
    if (t == 0) return 1;
    if (t != (t & -t)) {
        do {
            t -= t & -t;
        } while (t != (t & -t));
        t <<= 1;
    }
    return t;
}

每个循环直接剥离最低有效的1位。注意仅当带符号的数字以二进制补码编码时,此功能才有效。


1
您可以放心地替换whiledo/while并保存一个测试,因为您已经通过前面的if语句确认了初始测试。
seh 2015年

谢谢@seh; 改进了do while
Derek Illchuk 2015年

这里最快的方法,为什么不投票?虽然,我改为while(t > (t & -t))
primo

@primo至少在C#中,对于32位整数,可接受的答案平均快2.5倍以上。
NetMage

0

那这样的事情呢:

int pot = 1;
for (int i = 0; i < 31; i++, pot <<= 1)
    if (pot >= x)
        break;

0

您只需要找到最高有效位并将其左移一次即可。这是一个Python实现。我认为x86拥有获取MSB的指令,但是在这里,我将直接用Python来实现它。一旦有了MSB,就很容易。

>>> def msb(n):
...     result = -1
...     index = 0
...     while n:
...         bit = 1 << index
...         if bit & n:
...             result = index
...             n &= ~bit
...         index += 1
...     return result
...
>>> def next_pow(n):
...     return 1 << (msb(n) + 1)
...
>>> next_pow(1)
2
>>> next_pow(2)
4
>>> next_pow(3)
4
>>> next_pow(4)
8
>>> next_pow(123)
128
>>> next_pow(222)
256
>>>

谈论Python答案中的汇编没有任何价值,因为Python使用字节码...
yyny

0

忘掉这个!它使用循环!

     unsigned int nextPowerOf2 ( unsigned int u)
     {
         unsigned int v = 0x80000000; // supposed 32-bit unsigned int

         if (u < v) {
            while (v > u) v = v >> 1;
         }
         return (v << 1);  // return 0 if number is too big
     }

0
private static int nextHighestPower(int number){
    if((number & number-1)==0){
        return number;
    }
    else{
        int count=0;
        while(number!=0){
            number=number>>1;
            count++;
        }
        return 1<<count;
    }
}

-3
// n is the number
int min = (n&-n);
int nextPowerOfTwo = n+min;

这是个主意吗:将所有1填充到最大的“当前”位?如果是这样,那会是n+min+1吗?
Garet Claborn

这失败了。(5&-5)= 1; 5 + 1 = 6
奥拉西

-3
#define nextPowerOf2(x, n) (x + (n-1)) & ~(n-1)

甚至

#define nextPowerOf2(x, n)  x + (x & (n-1)) 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.