四舍五入到2的下一位


188

我想编写一个函数,该函数返回最接近的2的下一个幂。例如,如果我的输入为789,则输出应为1024。是否有任何方法可以在不使用任何循环而仅使用一些按位运算符的情况下实现这一目标?


4
请参阅此处以了解可能的解决方案:http
Stefan,2009年

4
通过澄清,您是否需要最接近的2的幂(即65会给您64,但是100会给您128)或最接近的幂(即65会给您128,所以100)?
金·里斯(

1
他们是与此匹配的多个问题。例如:stackoverflow.com/questions/364985/...
晏Droneaud


7
@Nathan您的链接比这个问题 8个月。
约瑟夫·昆西

Answers:


147

检查位扭曲黑客。您需要获取以2为底的对数,然后将其加1。32位值的示例:

向上舍入到2的下一个最高幂

unsigned int v; // compute the next highest power of 2 of 32-bit v

v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;

对其他宽度的扩展应该是显而易见的。


11
这不是最有效的解决方案,因为许多处理器具有用于计数前导零的特殊指令,可用于非常有效地计算log2。参见en.wikipedia.org/wiki/Find_first_set
西蒙(Simon)

7
@Simon:这是便携式解决方案。没有适用于所有体系结构的通用高效算法
phuclv 2013年

5
如果数字本身是2的幂怎么办?
Litherum 2013年

5
这个线程仍然被很好地引用,但是这个答案(和大多数其他答案)已经过时了。CPU有一条指令来帮助您(实际上是在那时吗?)。来自:jameshfisher.com/2018/03/30/round-up-power-2.html uint64_t next_pow2(uint64_t x) { return x == 1 ? 1 : 1<<(64-__builtin_clzl(x-1)); }对于32位:uint32_t next_pow2(uint32_t x) { return x == 1 ? 1 : 1<<(32-__builtin_clz(x-1)); }那就是如果您使用GCC(我认为是Clang?),那么花一些时间来是明智的找到对CLZ的调用,而不是复制粘贴所有选项。
MappaM

2
@MappaM这个答案仍然高度相关,并且是实现此目的的最佳便携式方法。如果x > UINT32_MAX且不是非分支的,则您的64位版本具有未定义的行为。另外,GCC和Clang -mtune=generic默认使用(与大多数发行版一样),因此您的代码将不会扩展到lzcntx86_64 上的指令-实际上,它会扩展到慢得多的东西(一个libgcc例程),除非您使用-march=native。因此,您建议的替代品是不可携带的,越野车,并且(通常)速度较慢。
克雷格·巴恩斯

76
next = pow(2, ceil(log(x)/log(2)));

这可以通过找到要加2的数字得到x来实现(取数字的对数,然后除以所需底数的对数,有关更多信息请参阅Wikipedia)。然后将其与ceil取整,以获取最接近的整数幂。

这是比其他地方链接的按位方法更通用的方法(即,速度更慢!),但是很了解数学,对吗?


3
在C99中,如果工具支持,也可以只使用log2。GCC和VS似乎不:(
马修

2
您缺少括号... next = pow(2,ceil(log(x)/ log(2)));
Matthieu Cormier,2012年

13
但是要注意浮动精度。 log(pow(2,29))/log(2)= 29.000000000000004,所以结果是2 30而不是返回 229。我认为这就是为什么存在log2函数的原因?
endlith 2013年

46
这样的代价可能至少需要200个周期,这甚至是不正确的。为什么会有这么多的投票?
Axel Gneiting

4
@SuperflyJon但它提到按位运算符,除非另有说明,否则我认为任何问题都暗示正确性。
BlackJack'17年

50
unsigned long upper_power_of_two(unsigned long v)
{
    v--;
    v |= v >> 1;
    v |= v >> 2;
    v |= v >> 4;
    v |= v >> 8;
    v |= v >> 16;
    v++;
    return v;

}

62
如果您已将其归因(除非您发现了它)会很好。它来自有点混乱的hacks页面。
佛罗里达州

3
是32位数字吗?扩展为64位?
乔纳森·莱夫勒

乔纳森(Jonathan),您需要对上半部分执行此操作,如果该值为零,请对下半部分进行操作。
佛罗里达州,

5
@florin,如果v是64位类型,难道您不可以在16的后面加上一个“ c | = v >> 32”吗?
Evan Teran

3
仅适用于特定位宽的代码应使用固定宽度类型,而不是最小宽度类型。此函数应采用并返回uint32_t
克雷格·巴恩斯

50

我认为这也可行:

int power = 1;
while(power < x)
    power*=2;

答案是power


19
公平地说,这个问题要求没有循环。但是,与其他一些功能一样,对于那些对性能不敏感的代码,快速,轻松地理解和验证正确的答案总是对我有利。
Tim MB

2
这不会返回最接近的2的幂,但是该幂立即大于X。仍然非常好
CoffeDeveloper

1
代替乘法,可以使用一些按位的“魔术”power <<= 1
vallentin '16

5
@Vallentin应该由编译器自动优化。
MarkWeston

4
如果x太大,请当心无限循环(即,没有足够的位数来表示2的下一个幂)。
alban

36

如果您使用的是GCC,则可能想看看Lockless Inc. 优化Optimizing next_pow2()函数。此页面介绍了一种使用内置函数builtin_clz()(计数前导零)并在以后直接使用x86(ia32)的方法。汇编程序指令bsr(反向位扫描),就像在另一个答案到gamedev网站链接中所描述的那样。此代码可能比以前的答案中描述的代码更快。

顺便说一句,如果您不打算使用汇编指令和64位数据类型,则可以使用此方法

/**
 * return the smallest power of two value
 * greater than x
 *
 * Input range:  [2..2147483648]
 * Output range: [2..2147483648]
 *
 */
__attribute__ ((const))
static inline uint32_t p2(uint32_t x)
{
#if 0
    assert(x > 1);
    assert(x <= ((UINT32_MAX/2) + 1));
#endif

    return 1 << (32 - __builtin_clz (x - 1));
}

3
请注意,这返回的最小幂2大于或等于x。将(x -1)更改为x会使函数返回大于x的较小的2的幂。
Guillaume13年

2
您可以_BitScanForward在Visual C ++上使用
KindDragon

您也可以使用__builtin_ctz()
MarkP '16

@MarkP __builtin_ctz()不能将任何2的非幂取整到下一个2的幂
Yann Droneaud '16

2
请在您的答案中添加指向其他编译器的内置按位函数的Wikipedia列表的链接:en.wikipedia.org/wiki/Find_first_set#Tool_and_library_support                                也请提供64位版本。我提出以下C ++ 11函数:              constexpr uint64_t nextPowerOfTwo64 (uint64_t x) { return 1ULL<<(sizeof(uint64_t) * 8 - __builtin_clzll(x)); }
olibre

15

再说一次,虽然我使用循环,但是比数学操作数快得多

两个“ floor”选项的功能:

int power = 1;
while (x >>= 1) power <<= 1;

两个“天花板”选项的功效:

int power = 2;
x--;    // <<-- UPDATED
while (x >>= 1) power <<= 1;

更新

如评论中所述,在结果错误的ceil地方存在错误。

以下是全部功能:

unsigned power_floor(unsigned x) {
    int power = 1;
    while (x >>= 1) power <<= 1;
    return power;
}

unsigned power_ceil(unsigned x) {
    if (x <= 1) return 1;
    int power = 2;
    x--;
    while (x >>= 1) power <<= 1;
    return power;
}

2
如果x是2的幂,则结果不正确。需要一个微型仪器来测试输入是否为2的幂。#define ISPOW2(x) ((x) > 0 && !((x) & (x-1)))
pgplus1628

@zorksylar更有效的方式是if (x == 0) return 1; /* Or 0 (Which is what I use) */ x--; /* Rest of program */
yyny

好的解决方案!但power of two "ceil" option不正确。例如,当x = 2结果应24
MZD

10

对于任何未签名的类型,请在Bit Twiddling Hacks的基础上进行构建:

#include <climits>
#include <type_traits>

template <typename UnsignedType>
UnsignedType round_up_to_power_of_2(UnsignedType v) {
  static_assert(std::is_unsigned<UnsignedType>::value, "Only works for unsigned types");
  v--;
  for (size_t i = 1; i < sizeof(v) * CHAR_BIT; i *= 2) //Prefer size_t "Warning comparison between signed and unsigned integer"
  {
    v |= v >> i;
  }
  return ++v;
}

编译器在编译时就知道迭代次数,因此实际上并没有循环。


4
请注意,问题是关于C的
。– martinkunev

@martinkunev只需替换UnsignedType并手动进行处理即可。我非常确定C程序员可以忽略该std::is_unsigned<UnsignedType>::value断言而扩展此简单模板。
user877329 2015年

2
@ user877329当然,这将是很好的在JavaScript中的答案为好,以防万一有人想那意思就是C.
martinkunev

@martinkunev JavaScript中的UnsignedType?无论如何,该解决方案说明了如何对任何UnsignedType执行此操作,并且它恰好是用C ++编写的,而不是伪代码[sizeof(v)* CHAR_BIT,而不是诸如UnsignedType对象中的位数)。
user877329 2015年

9

对于IEEE浮动,您将可以执行以下操作。

int next_power_of_two(float a_F){
    int f = *(int*)&a_F;
    int b = f << 9 != 0; // If we're a power of two this is 0, otherwise this is 1

    f >>= 23; // remove factional part of floating point number
    f -= 127; // subtract 127 (the bias) from the exponent

    // adds one to the exponent if were not a power of two, 
    // then raises our new exponent to the power of two again.
    return (1 << (f + b)); 
}

如果您需要整数解决方案并且可以使用内联汇编,则BSR将在x86上为您提供整数的log2。它计算设置了多少个右位,恰好等于该数字的log2。其他处理器也有类似的指令(通常),例如CLZ,并且取决于您的编译器,可能有一个内在的函数可以为您完成工作。


这是一个有趣的eventhough不相关的问题(我想只四舍五入整数),会尝试这一个..
纳文

阅读了有关花车的维基百科文章后,提出了它。除此之外,我用它来计算整数精度的平方根。也不错,但更无关。
Jasper Bekkers,2009年

这违反了严格的别名规则。在某些编译器上,它可能不起作用或发出警告。
martinkunev


5
/*
** http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
*/
#define __LOG2A(s) ((s &0xffffffff00000000) ? (32 +__LOG2B(s >>32)): (__LOG2B(s)))
#define __LOG2B(s) ((s &0xffff0000)         ? (16 +__LOG2C(s >>16)): (__LOG2C(s)))
#define __LOG2C(s) ((s &0xff00)             ? (8  +__LOG2D(s >>8)) : (__LOG2D(s)))
#define __LOG2D(s) ((s &0xf0)               ? (4  +__LOG2E(s >>4)) : (__LOG2E(s)))
#define __LOG2E(s) ((s &0xc)                ? (2  +__LOG2F(s >>2)) : (__LOG2F(s)))
#define __LOG2F(s) ((s &0x2)                ? (1)                  : (0))

#define LOG2_UINT64 __LOG2A
#define LOG2_UINT32 __LOG2B
#define LOG2_UINT16 __LOG2C
#define LOG2_UINT8  __LOG2D

static inline uint64_t
next_power_of_2(uint64_t i)
{
#if defined(__GNUC__)
    return 1UL <<(1 +(63 -__builtin_clzl(i -1)));
#else
    i =i -1;
    i =LOG2_UINT64(i);
    return 1UL <<(1 +i);
#endif
}

如果您不想冒险进入未定义行为的领域,则输入值必须在1到2 ^ 63之间。该宏对于在编译时设置常量也很有用。


这可能是最糟糕的解决方案(它在64位常量上也缺少ULL后缀)。在所有情况下,这将为每个输入生成32个测试。最好使用while循环,它将始终更快或保持相同的速度。
xryl669 '16

1
但是...如果输入为常数,则可以由预处理器进行评估,因此在运行时为零操作!
迈克尔

4

为了完整起见,这里是沼泽标准C中的浮点实现。

double next_power_of_two(double value) {
    int exp;
    if(frexp(value, &exp) == 0.5) {
        // Omit this case to round precise powers of two up to the *next* power
        return value;
    }
    return ldexp(1.0, exp);
}

1
随机浏览器,如果您阅读此注释,请选择此代码。这显然是最好的答案,没有特殊说明,没有任何花招,只是高效,可移植和标准的代码。猜测为什么没有其他人支持它^^
CoffeDeveloper 2015年

5
随机浏览器,如果没有专门的浮点硬件,这将变得非常缓慢。在x86上,您可以使用twiddling在此代码周围运行。rep bsr ecx,eax; mov eax,0; cmovnz eax,2; shl eax,cl快25倍
约翰

4

C / C ++中针对整数输入的高效Microsoft(例如Visual Studio 2017)特定解决方案。在检查最高有效1位的位置之前,通过递减来处理输入与两个值的幂完全匹配的情况。

inline unsigned int ExpandToPowerOf2(unsigned int Value)
{
    unsigned long Index;
    _BitScanReverse(&Index, Value - 1);
    return (1U << (Index + 1));
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

#if defined(WIN64) // The _BitScanReverse64 intrinsic is only available for 64 bit builds because it depends on x64

inline unsigned long long ExpandToPowerOf2(unsigned long long Value)
{
    unsigned long Index;
    _BitScanReverse64(&Index, Value - 1);
    return (1ULL << (Index + 1));
}

#endif

这会为Intel处理器生成5条左右内联指令,类似于以下内容:

dec eax
bsr rcx, rax
inc ecx
mov eax, 1
shl rax, cl

显然,没有对Visual Studio C ++编译器进行编码以优化其编译时值,但似乎并没有很多说明。

编辑:

如果您希望输入值1产生1(2的零次幂),则对上面的代码进行小的修改仍会生成无分支的直接指令。

inline unsigned int ExpandToPowerOf2(unsigned int Value)
{
    unsigned long Index;
    _BitScanReverse(&Index, --Value);
    if (Value == 0)
        Index = (unsigned long) -1;
    return (1U << (Index + 1));
}

仅生成更多指令。诀窍在于,Index可以由后面带有cmove指令的测试代替。


一个小错误:应该返回1代表1,但不是。
0kcats

谢谢。在为其开发的应用程序中,当输入1时,我们明确需要2等于第一幂。可以将1作为带条件的特例,而无需生成太多我想像的指令。
NoelC

更新了答案,包括为1的输入值返回1版本
NoelC

3

在x86中,可以使用sse4位操作指令来使其快速。

//assume input is in eax
popcnt edx,eax
lzcnt ecx,eax
cmp edx,1
jle @done       //popcnt says its a power of 2, return input unchanged
mov eax,2
shl eax,cl
@done: rep ret

在c中,您可以使用匹配的内在函数。


没有用,但真棒!
马可(Marco)

3

这是我在C语言中的解决方案。希望这会有所帮助!

int next_power_of_two(int n) {
    int i = 0;
    for (--n; n > 0; n >>= 1) {
        i++;
    }
    return 1 << i;
}

0

许多处理器体系结构都支持log base 2或非常相似的操作– count leading zeros。许多编译器都具有内在函数。参见https://en.wikipedia.org/wiki/Find_first_set


这不是要找到最高的设置位(= bsr)或计算前导零。他想四舍五入到最接近的2的幂。答案是“减去1,然后进行bsr并向左移1”。
Flo

0

假设您有一个好的编译器,并且此时它可以在我之前进行一些调试,但是无论如何,这都可以!!!

    // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
    #define SH1(v)  ((v-1) | ((v-1) >> 1))            // accidently came up w/ this...
    #define SH2(v)  ((v) | ((v) >> 2))
    #define SH4(v)  ((v) | ((v) >> 4))
    #define SH8(v)  ((v) | ((v) >> 8))
    #define SH16(v) ((v) | ((v) >> 16))
    #define OP(v) (SH16(SH8(SH4(SH2(SH1(v))))))         

    #define CB0(v)   ((v) - (((v) >> 1) & 0x55555555))
    #define CB1(v)   (((v) & 0x33333333) + (((v) >> 2) & 0x33333333))
    #define CB2(v)   ((((v) + ((v) >> 4) & 0xF0F0F0F) * 0x1010101) >> 24)
    #define CBSET(v) (CB2(CB1(CB0((v)))))
    #define FLOG2(v) (CBSET(OP(v)))

测试代码如下:

#include <iostream>

using namespace std;

// http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
#define SH1(v)  ((v-1) | ((v-1) >> 1))  // accidently guess this...
#define SH2(v)  ((v) | ((v) >> 2))
#define SH4(v)  ((v) | ((v) >> 4))
#define SH8(v)  ((v) | ((v) >> 8))
#define SH16(v) ((v) | ((v) >> 16))
#define OP(v) (SH16(SH8(SH4(SH2(SH1(v))))))         

#define CB0(v)   ((v) - (((v) >> 1) & 0x55555555))
#define CB1(v)   (((v) & 0x33333333) + (((v) >> 2) & 0x33333333))
#define CB2(v)   ((((v) + ((v) >> 4) & 0xF0F0F0F) * 0x1010101) >> 24)
#define CBSET(v) (CB2(CB1(CB0((v)))))
#define FLOG2(v) (CBSET(OP(v))) 

#define SZ4         FLOG2(4)
#define SZ6         FLOG2(6)
#define SZ7         FLOG2(7)
#define SZ8         FLOG2(8) 
#define SZ9         FLOG2(9)
#define SZ16        FLOG2(16)
#define SZ17        FLOG2(17)
#define SZ127       FLOG2(127)
#define SZ1023      FLOG2(1023)
#define SZ1024      FLOG2(1024)
#define SZ2_17      FLOG2((1ul << 17))  // 
#define SZ_LOG2     FLOG2(SZ)

#define DBG_PRINT(x) do { std::printf("Line:%-4d" "  %10s = %-10d\n", __LINE__, #x, x); } while(0);

uint32_t arrTble[FLOG2(63)];

int main(){
    int8_t n;

    DBG_PRINT(SZ4);    
    DBG_PRINT(SZ6);    
    DBG_PRINT(SZ7);    
    DBG_PRINT(SZ8);    
    DBG_PRINT(SZ9); 
    DBG_PRINT(SZ16);
    DBG_PRINT(SZ17);
    DBG_PRINT(SZ127);
    DBG_PRINT(SZ1023);
    DBG_PRINT(SZ1024);
    DBG_PRINT(SZ2_17);

    return(0);
}

输出:

Line:39           SZ4 = 2
Line:40           SZ6 = 3
Line:41           SZ7 = 3
Line:42           SZ8 = 3
Line:43           SZ9 = 4
Line:44          SZ16 = 4
Line:45          SZ17 = 5
Line:46         SZ127 = 7
Line:47        SZ1023 = 10
Line:48        SZ1024 = 10
Line:49        SZ2_16 = 17

0

我正在尝试获得最接近的2的低次幂并进行此功能。可能对您有帮助,只需将最近的较低数字乘以2即可获得最近的2的高次幂

int nearest_upper_power(int number){
    int temp=number;
    while((number&(number-1))!=0){
        temp<<=1;
        number&=temp;
    }
    //Here number is closest lower power 
    number*=2;
    return number;
}


0

@YannDroneaud答案的一种变体x==1,仅对x86平台,编译器,gcc或clang有效:

__attribute__ ((const))
static inline uint32_t p2(uint32_t x)
{
#if 0
    assert(x > 0);
    assert(x <= ((UINT32_MAX/2) + 1));
#endif
  int clz;
  uint32_t xm1 = x-1;
  asm(
    "lzcnt %1,%0"
    :"=r" (clz)
    :"rm" (xm1)
    :"cc"
    );
    return 1 << (32 - clz);
}

0

如果输入是常量表达式,这就是我用来使它成为常量表达式的内容。

#define uptopow2_0(v) ((v) - 1)
#define uptopow2_1(v) (uptopow2_0(v) | uptopow2_0(v) >> 1)
#define uptopow2_2(v) (uptopow2_1(v) | uptopow2_1(v) >> 2)
#define uptopow2_3(v) (uptopow2_2(v) | uptopow2_2(v) >> 4)
#define uptopow2_4(v) (uptopow2_3(v) | uptopow2_3(v) >> 8)
#define uptopow2_5(v) (uptopow2_4(v) | uptopow2_4(v) >> 16)

#define uptopow2(v) (uptopow2_5(v) + 1)  /* this is the one programmer uses */

例如,这样的表达式:

uptopow2(sizeof (struct foo))

将很好地减少到一个常数。



0

将其转换为浮点数,然后使用.hex()来显示标准化的IEEE表示形式。

>>> float(789).hex() '0x1.8a80000000000p+9'

然后只需提取指数并加1。

>>> int(float(789).hex().split('p+')[1]) + 1 10

并提高2的力量。

>>> 2 ** (int(float(789).hex().split('p+')[1]) + 1) 1024


请注意,此答案在python中
David Wallace

0
import sys


def is_power2(x):
    return x > 0 and ((x & (x - 1)) == 0)


def find_nearest_power2(x):
    if x <= 0:
        raise ValueError("invalid input")
    if is_power2(x):
        return x
    else:
        bits = get_bits(x)
        upper = 1 << (bits)
        lower = 1 << (bits - 1)
        mid = (upper + lower) // 2
        if (x - mid) > 0:
            return upper
        else:
            return lower


def get_bits(x):
    """return number of bits in binary representation"""
    if x < 0:
        raise ValueError("invalid input: input should be positive integer")
    count = 0
    while (x != 0):
        try:
            x = x >> 1
        except TypeError as error:
            print(error, "input should be of type integer")
            sys.exit(1)
        count += 1
    return count

-1

如果您需要OpenGL相关内容:

/* Compute the nearest power of 2 number that is 
 * less than or equal to the value passed in. 
 */
static GLuint 
nearestPower( GLuint value )
{
    int i = 1;

    if (value == 0) return -1;      /* Error! */
    for (;;) {
         if (value == 1) return i;
         else if (value == 3) return i*4;
         value >>= 1; i *= 2;
    }
}

8
“ for”是一个循环。
佛罗里达州

1
弗洛林:是的。并在这里用作循环,不是吗?
Tamas Czinege,2009年

9
DrJokepu-我想弗洛林在这里要说的是,OP要求一种无环解决方案
Eli Bendersky,2009年

-1

如果要单行模板。这里是

int nxt_po2(int n) { return 1 + (n|=(n|=(n|=(n|=(n|=(n-=1)>>1)>>2)>>4)>>8)>>16); }

要么

int nxt_po2(int n) { return 1 + (n|=(n|=(n|=(n|=(n|=(n-=1)>>(1<<0))>>(1<<1))>>(1<<2))>>(1<<3))>>(1<<4)); }

这是C或C ++中未定义的行为,将导致错误。n多次修改而没有序列点是无效的。您写的好像是n-=1应该先发生的,但唯一的保证是n;和后面的括号中包含它的新值不会改变它。
sam hocevar,
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.