我想写一个函数,返回最接近的2的次幂数。例如,如果我的输入是789,输出应该是1024。有没有办法不使用任何循环,而只是使用一些位运算符?
当前回答
/*
** 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之间。宏在编译时设置常量也很有用。
其他回答
在x86中,你可以使用sse4位操作指令来提高速度。
//assume input is in eax
mov ecx,31
popcnt edx,eax //cycle 1
lzcnt eax,eax //cycle 2
sub ecx,eax
mov eax,1
cmp edx,1 //cycle 3
jle @done //cycle 4 - popcnt says its a power of 2, return input unchanged
shl eax,cl //cycle 5
@done: rep ret //cycle 5
在c中,您可以使用匹配的intrinsic。
或者无跳转,通过避免跳转导致的错误预测来加快速度,但通过延长依赖链来减慢速度。计时,看看哪种代码最适合您。
//assume input is in eax
mov ecx,31
popcnt edx,eax //cycle 1
lzcnt eax,eax
sub ecx,eax
mov eax,1 //cycle 2
cmp edx,1
mov edx,0 //cycle 3
cmovle ecx,edx //cycle 4 - ensure eax does not change
shl eax,cl
@done: rep ret //cycle 5
这是我用来让它成为一个常数表达式的,如果输入是一个常数表达式的话。
#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))
会很好地化简为常数。
对于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,根据你的编译器,可能有一个内在的可用指令来为你做这项工作。
对于任何unsigned类型,构建在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;
}
这里并没有真正的循环,因为编译器在编译时知道迭代的次数。
尽管问题标注为c,我的5分钱。幸运的是,c++ 20将包含std::ceil2和std::floor2(参见这里)。它是consexpr模板函数,目前的GCC实现使用位移位和工作于任何整型无符号类型。
推荐文章
- 为什么函数指针定义可以使用任意数量的&号或星号* ?
- 什么是可重入函数?
- 如何在C中将数组初始化为0 ?
- 为什么512x512矩阵的转置比513x513矩阵的转置慢得多?
- Typedef定长数组
- 在C语言中通过引用传递
- sizeof(某个指针)总是等于4吗?
- 为什么星号在变量名之前,而不是在类型之后?
- 何时使用内联函数,何时不使用它?
- 在C语言中如何比较结构是否相等?
- 在套接字编程中AF_INET和PF_INET的区别是什么?
- #在C中定义打印调试宏?
- read()和recv(), send()和write()之间有什么区别?
- 为什么0 < -0x80000000?
- 静态const vs #define