计算“2的幂”数字的最快方法?

时间:2014-01-29 17:45:14

标签: c++ math

找到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
}

5 个答案:

答案 0 :(得分:8)

以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);
}

答案 1 :(得分:4)

此操作非常适合处理器供应商提供硬件支持。查看find first set。编译器供应商为此提供了特定的功能,遗憾的是似乎没有标准如何命名它。因此,如果您需要最高性能,则必须创建依赖于编译器的代码:

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

答案 2 :(得分:1)

如果输入值仅为2^n n - 整数,则查找n的最佳方法是将哈希表与perfect hash function一起使用。在这种情况下,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_);   
}

答案 3 :(得分:0)

您的版本很好,但正如您所推测的那样,它的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++,因此这是一个使用模板的版本,试图找出初始掩码&amp;步骤:

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架构的一个亮点,但在嵌入式解决方案中我已经找到了这一点,在最后一个案例中我解决了与此类似的搜索,甚至是{ {1}}对于中断来说太慢了,我们不得不使用除法和征服以及表查找的组合来挤出最后几个周期。

答案 4 :(得分:0)

如果您知道它确实是2的幂(很容易验证), 请尝试以下变体。 完整说明如下: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];
}