计算"power of 2"数字使用的功率的最快方法?

Fastest way of computing the power that a "power of 2" number used?

本文关键字:功率 方法 of power 数字 计算      更新时间:2023-10-16

对于某个数字(即2的幂),找到2的幂的最快方法是什么?

我不太擅长数学,所以我不知道如何最好地描述它。但函数看起来类似于x = 2^y,其中y是输出,x是输入。这是一个真相表,如果这有助于解释它的话,它会是什么样子。

0 = f(1)
1 = f(2)
2 = f(4)
3 = f(8)
...
8 = f(256)
9 = f(512)

我已经制作了一个函数来实现这一点,但我担心它不是很高效(或者说不优雅)。有没有一种更简单、更有效的方法来做到这一点?我用它来计算纹理的哪个区域用于缓冲绘制的方式,所以它对每个绘制的对象至少调用一次。以下是我迄今为止所做的功能:

uint32 getThePowerOfTwo(uint32 value){
for(uint32 n = 0; n < 32; ++n){
if(value <= (1 << n)){
return n;
}
}
return 32; // should never be called
}

基于woolstar的答案——我想知道查找表的二进制搜索是否会稍微快一点?(看起来漂亮多了)。。。

int getThePowerOfTwo(int value) {
static constexpr int twos[] = {
1<<0,  1<<1,  1<<2,  1<<3,  1<<4,  1<<5,  1<<6,  1<<7,
1<<8,  1<<9,  1<<10, 1<<11, 1<<12, 1<<13, 1<<14, 1<<15,
1<<16, 1<<17, 1<<18, 1<<19, 1<<20, 1<<21, 1<<22, 1<<23,
1<<24, 1<<25, 1<<26, 1<<27, 1<<28, 1<<29, 1<<30, 1<<31
};
return std::lower_bound(std::begin(twos), std::end(twos), value) - std::begin(twos);
}

此操作非常流行,处理器供应商可以为其提供硬件支持。查看第一组。编译器供应商为此提供了特定的函数,不幸的是,似乎没有标准的命名方法。因此,如果你需要最大限度的性能,你必须创建依赖编译器的代码:

# ifdef __GNUC__  
return __builtin_ffs( x ) - 1; // GCC
#endif
#ifdef _MSC_VER
return CHAR_BIT * sizeof(x)-__lzcnt( x ); // Visual studio
#endif

如果输入值仅为2^n,其中n-整数,则查找n的最佳方法是使用具有完美哈希函数的哈希表。在这种情况下,32个无符号整数的散列函数可以定义为value % 37

template < size_t _Div >
std::array < uint8_t, _Div > build_hash()
{
std::array < uint8_t, _Div > hash_;
std::fill(hash_.begin(), hash_.end(), std::numeric_limits<uint8_t>::max());
for (size_t index_ = 0; index_ < 32; ++index_)
hash_[(1 << index_) % _Div] = index_;
return hash_;
}
uint8_t hash_log2(uint32_t value_)
{
static const std::array < uint8_t, 37 > hash_ = build_hash<37> ();
return hash_[value_%37];
}

检查

int main()
{
for (size_t index_ = 0; index_ < 32; ++index_)
assert(hash_log2(1 << index_) == index_);   
}

您的版本还不错,但正如您所推测的,它的O(n)意味着它每一位都要经过一步循环。你可以做得更好。要进入下一步,请尝试进行相当于分而治之的操作:

unsigned int log2(unsigned int value)
{
unsigned int val = 0 ;
unsigned int mask= 0xffff0000 ;
unsigned int step= 16 ;
while ( value )
{
if ( value & mask ) { val += step ;  value &= ~ mask ; }
step /= 2 ;
if ( step ) { mask >>= step ; } else { mask >>= 1 ; }
}
return val ;
}

由于我们只是在寻找最高的比特,我们开始问单词的上半部分是否有比特。如果有,我们可以丢弃所有较低的比特,否则我们只会缩小搜索范围。

由于问题被标记为C++,这里有一个使用模板的版本,它试图找出初始掩码&步骤:

template <typename T>
T log2(T val)
{
T result = 0 ;
T step= ( 4 * sizeof( T ) ) ;  // half the number of bits
T mask= ~ 0L - ( ( 1L << ( 4 * sizeof( T )) ) -1 ) ;
while ( val && step )
{
if ( val & mask ) { result += step ;  val >>= step ; }
mask >>= ( step + 1) / 2 ;
step /= 2 ; 
}
return result ;
}

虽然这两个版本的性能在现代x86体系结构上都只是昙花一现,但这在嵌入式解决方案中已经出现了,在上一个案例中,我解决了一个与此非常相似的搜索问题,即使是O(log N)也太慢了,无法中断,我们不得不使用分治加表查找的组合来挤出最后几个周期。

如果你知道它确实是二的幂(这很容易验证),试试下面的变体。完整描述如下:http://sree.kotay.com/2007/04/shift-registers-and-de-bruijn-sequences_10.html

//table
static const int8 xs_KotayBits[32] =    {
0,  1,  2, 16,  3,  6, 17, 21,
14,  4,  7,  9, 18, 11, 22, 26,
31, 15,  5, 20, 13,  8, 10, 25,
30, 19, 12, 24, 29, 23, 28, 27
};

//only works for powers of 2 inputs
static inline int32 xs_ILogPow2 (int32 v){
assert (v && (v&(v-1)==0));
//constant is binary 10 01010 11010 00110 01110 11111
return xs_KotayBits[(uint32(v)*uint32( 0x04ad19df ))>>27];
}