四舍五入到下一个2的幂

Nav*_*een 170 c optimization bit-manipulation

我想写一个函数,返回最近的2个数的下一个幂.例如,如果我的输入是789,输出应该是1024.有没有任何方法可以实现这一点而不使用任何循环但只使用一些按位运算符?

flo*_*rin 129

检查Bit Twiddling Hacks.你需要得到基数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++;
Run Code Online (Sandbox Code Playgroud)

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

  • 该线程仍然被很好地引用,但是这个答案(以及大多数其他答案)已经过时了。CPU 有一条指令可以帮助解决这个问题(实际上已经在那个时候?)。来自:https://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 的调用,而不是复制粘贴所有选项。 (13认同)
  • 这不是最有效的解决方案,因为许多处理器都有特殊的指令来计算前导零,这可以用来非常有效地计算log2.请参阅https://en.wikipedia.org/wiki/Find_first_set (9认同)
  • @Simon:这是便携式解决方案.对于所有体系结构,没有通用的高效算法 (5认同)
  • 如果数字本身是2的幂,该怎么办? (5认同)
  • 链接已经死了. (3认同)
  • @MappaM 这个答案仍然是高度相关的,并且是最好的便携式方法。如果“x > UINT32_MAX”且不是无分支,则您的 64 位版本具有未定义的行为。另外,GCC 和 Clang 默认使用 `-mtune=generic` (大多数发行版也是如此),因此您的代码不会扩展到 x86_64 上的 `lzcnt` 指令 —— 它实际上会扩展到慢得多的东西(一个 libgcc 例程) )除非你使用类似“-march=native”的东西。因此,您建议的替代品是不可移植的、有缺陷的并且(通常)速度较慢。 (3认同)
  • @MappaM 这听起来像是“在我的机器上运行”的情况。对我来说,“next_pow2(1ULL << 34)”返回 0,原因与“_clzl”与“_clz”无关。虽然你的函数修复起来很简单,但我想要表达的观点很简单——如果你要称其他人的代码“高度过时”,你应该首先确保你自己的代码经过测试并且可以工作,而不仅仅是一个有越野车的copypasta。 (2认同)

Pau*_*xon 77

next = pow(2, ceil(log(x)/log(2)));
Run Code Online (Sandbox Code Playgroud)

这可以通过找到你已经提高2的数字来获得x(记录数字的对数,除以所需基数的日志,更多信息请参阅维基百科).然后用ceil向上舍入以获得最接近的整数幂.

这是一个比其他地方链接的按位方法更通用(即更慢!)的方法,但很高兴知道数学,嗯?

  • 这个成本至少可能是200个周期而且甚至都不正确.为什么会有这么多的赞成? (36认同)
  • 但是要小心浮动精度.`log(pow(2,29))/ log(2)`= 29.000000000000004,结果是2**30而不是返回2**29.我认为这就是为什么存在log2函数的原因? (10认同)
  • @SuperflyJon但它提到了按位运算符,我认为任何问题都暗示了正确性,除非另有说明. (4认同)
  • 可以将对数与位运算符组合: 1 << ceil(log2(x)) (3认同)
  • 从C99开始,如果您的工具支持,您也可以使用[log2](http://www.cprogramming.com/fod/log2.html).海湾合作委员会和VS似乎没有:( (2认同)
  • 你错过了一个括号... next = pow(2,ceil(log(x)/ log(2))); (2认同)
  • 更快的是 `powf(2, ceilf(log2f(val)))` (2认同)
  • 也许是因为问题没有提到性能? (2认同)
  • 请更新答案并使用 log2()。不看评论犯了一个错误,结果在CP大赛中惨败。:( (2认同)

Ecl*_*pse 49

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;

}
Run Code Online (Sandbox Code Playgroud)

  • 如果你把它归为它会很好(除非你发现它).它来自尖锐的黑客页面. (53认同)
  • @florin,如果v是64位类型,你不能只在16之后加一个"c | = v >> 32"吗? (5认同)
  • 仅适用于特定位宽的代码应使用固定宽度类型,而不是最小宽度类型。这个函数应该接受并返回一个`uint32_t`。 (3认同)
  • 那是32位数吗?扩展为64位? (2认同)
  • 使用signed int 时要小心。值 > 0x4000_0000_0000_0000 将返回 0x8000_0000_0000_0000。 (2认同)

小智 48

我认为这也有效:

int power = 1;
while(power < x)
    power*=2;
Run Code Online (Sandbox Code Playgroud)

答案是power.

  • 很公平,问题没有循环.但是,与其他一些功能一样聪明,对于性能不敏感的代码,快速,容易理解并验证为正确的答案总能赢得我的支持. (17认同)
  • @Vallentin应该由编译器自动优化. (4认同)
  • 除了乘法之外,还可以使用一些按位“魔法”代替`power &lt;&lt;= 1` (3认同)
  • 这并没有返回最近的2的幂,但是它的力量立即大于X.仍然非常好 (2认同)
  • 如果x太大(即没有足够的位来表示2的下一个幂),请当心无限循环。 (2认同)

Yan*_*aud 31

如果你正在使用GCC,你可能想看一下Lockless公司优化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));
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,这会返回大于或等于x的2的最小幂。将(x -1)更改为x会使函数返回大于x的2的较小幂。 (2认同)
  • 你可以在 Visual C++ 上使用 `_BitScanForward` (2认同)
  • 请在您的答案中添加指向其他编译器的内置按位函数的维基百科列表的链接:https://en.wikipedia.org/wiki/Find_first_set#Tool_and_library_support 请同时提供 64 位版本。我提出以下 C++11 函数:`constexpr uint64_t nextPowerOfTwo64 (uint64_t x) { return 1ULL&lt;&lt;(sizeof(uint64_t) * 8 - __builtin_clzll(x)); }` (2认同)

Jak*_*idt 31

在标准中c++20,这包含在<bit>. 答案很简单

#include <bit>
unsigned long upper_power_of_two(unsigned long v)
{
    return std::bit_ceil(v);
}
Run Code Online (Sandbox Code Playgroud)

注意: 我给出的解决方案是 for c++,而不是,我会给出这个c问题的答案,但它作为这个问题的重复项被关闭!

  • 我同意_“在C 中解决这个问题”_ **不是** _“在C ++ 中解决这个问题”_ 的重复。你正在证明这一点。所以我[重新打开了您链接到的副本](/sf/ask/25548981/ a-给定)。请随意将您的 C++ 答案移至此处。 (4认同)

vlk*_*vlk 15

还有一个,虽然我使用了循环,但是它比数学操作数快得多

两个"地板"选项的力量:

int power = 1;
while (x >>= 1) power <<= 1;
Run Code Online (Sandbox Code Playgroud)

两个"ceil"选项的力量:

int power = 2;
x--;    // <<-- UPDATED
while (x >>= 1) power <<= 1;
Run Code Online (Sandbox Code Playgroud)

UPDATE

如评论中所述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;
}
Run Code Online (Sandbox Code Playgroud)

  • 如果`x`是2的幂,则结果不正确.如果输入是2的幂则需要微测试.`#define ISPOW2(x)((x)> 0 &&!((x)&(x-1)))` (2认同)

kre*_*ieg 11

尽管这个问题在c这里被标记为我的五美分。幸运的是,C++ 20 将包含std::ceil2std::floor2(请参阅此处)。它是consexpr模板函数,当前的GCC 实现使用位移并适用于任何整数无符号类型。

  • 他们最近将其重命名为“bit_ceil”http://open-std.org/JTC1/SC22/WG21/docs/papers/2020/p1956r1.pdf (6认同)
  • 现在,C++20 中的“&lt;bit&gt;”标头中提供了“bit_floor”和“bit_ceil”。https://en.cppreference.com/w/cpp/header/bit (2认同)

小智 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;
}
Run Code Online (Sandbox Code Playgroud)

由于编译器在编译时知道迭代次数,因此实际上没有循环.

  • @user877329 当然,如果有人想将它翻译成 C,那么在 Javascript 中也有答案会很好。 (3认同)
  • 请注意,问题是关于C. (2认同)

Jas*_*ers 8

对于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)); 
}
Run Code Online (Sandbox Code Playgroud)

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


小智 8

这是我的 C 解决方案。希望这有帮助!

int next_power_of_two(int n) {
    int i = 0;
    for (--n; n > 0; n >>= 1) {
        i++;
    }
    return 1 << i;
}
Run Code Online (Sandbox Code Playgroud)


Joh*_*ica 6

在 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
Run Code Online (Sandbox Code Playgroud)

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


Phi*_*Fry 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
}
Run Code Online (Sandbox Code Playgroud)

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


doy*_*nax 5

为了完整起见,这里是沼泽标准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);
}
Run Code Online (Sandbox Code Playgroud)

  • 随机浏览器,如果您没有专门的浮点硬件,这将会变得非常慢.在x86上,您可以使用bit twiddling围绕此代码运行圆圈.`rep bsr ecx,eax; mov eax,0; cmovnz eax,2; shl eax,cl`快约25倍. (5认同)