C 64位整数log2的快速计算

C 64位整数log2的快速计算,c,64-bit,bit-manipulation,32bit-64bit,lookup,C,64 Bit,Bit Manipulation,32bit 64bit,Lookup,伟大的编程资源Bit Twiddling Hacks()提出了以下方法来计算32位整数的log2: #define LT(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n static const char LogTable256[256] = { -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, LT(4), LT(5), LT(5), LT(6), LT(6), LT(6),

伟大的编程资源Bit Twiddling Hacks()提出了以下方法来计算32位整数的log2:

#define LT(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n
static const char LogTable256[256] = 
{
    -1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
    LT(4), LT(5), LT(5), LT(6), LT(6), LT(6), LT(6),
    LT(7), LT(7), LT(7), LT(7), LT(7), LT(7), LT(7), LT(7)
};

unsigned int v; // 32-bit word to find the log of
unsigned r;     // r will be lg(v)
register unsigned int t, tt; // temporaries
if (tt = v >> 16)
{
    r = (t = tt >> 8) ? 24 + LogTable256[t] : 16 + LogTable256[tt];
}
else 
{
    r = (t = v >> 8) ? 8 + LogTable256[t] : LogTable256[v];
}
并提到

查找表方法只需要大约7次操作就可以找到日志 一个32位的值。如果扩展为64位数量,则需要 大约有9次行动

但是,遗憾的是,它没有给出任何额外的信息,说明如何将算法扩展到64位整数


有关于这种64位算法的任何提示吗?

该算法基本上会找出哪个字节包含最重要的1位,然后在查找中查找该字节以查找该字节的日志,然后将其添加到该字节的位置

下面是32位算法的简化版本:

if (tt = v >> 16)
{
    if (t = tt >> 8)
    {
        r = 24 + LogTable256[t];
    }
    else
    {
        r = 16 + LogTable256[tt];
    }
}
else 
{
    if (t = v >> 8)
    {
        r = 8 + LogTable256[t];
    }
    else
    {
        r = LogTable256[v];
    }
}
if (ttt = v >> 32)
{
    if (tt = ttt >> 16)
    {
        if (t = tt >> 8)
        {
            r = 56 + LogTable256[t];
        }
        else
        {
            r = 48 + LogTable256[tt];
        }
    }
    else 
    {
        if (t = ttt >> 8)
        {
            r = 40 + LogTable256[t];
        }
        else
        {
            r = 32 + LogTable256[ttt];
        }
    }
}
else
{
    if (tt = v >> 16)
    {
        if (t = tt >> 8)
        {
            r = 24 + LogTable256[t];
        }
        else
        {
            r = 16 + LogTable256[tt];
        }
    }
    else 
    {
        if (t = v >> 8)
        {
            r = 8 + LogTable256[t];
        }
        else
        {
            r = LogTable256[v];
        }
    }
}
这是等效的64位算法:

if (tt = v >> 16)
{
    if (t = tt >> 8)
    {
        r = 24 + LogTable256[t];
    }
    else
    {
        r = 16 + LogTable256[tt];
    }
}
else 
{
    if (t = v >> 8)
    {
        r = 8 + LogTable256[t];
    }
    else
    {
        r = LogTable256[v];
    }
}
if (ttt = v >> 32)
{
    if (tt = ttt >> 16)
    {
        if (t = tt >> 8)
        {
            r = 56 + LogTable256[t];
        }
        else
        {
            r = 48 + LogTable256[tt];
        }
    }
    else 
    {
        if (t = ttt >> 8)
        {
            r = 40 + LogTable256[t];
        }
        else
        {
            r = 32 + LogTable256[ttt];
        }
    }
}
else
{
    if (tt = v >> 16)
    {
        if (t = tt >> 8)
        {
            r = 24 + LogTable256[t];
        }
        else
        {
            r = 16 + LogTable256[tt];
        }
    }
    else 
    {
        if (t = v >> 8)
        {
            r = 8 + LogTable256[t];
        }
        else
        {
            r = LogTable256[v];
        }
    }
}
我想出了一个算法,适用于任何尺寸类型,我认为比原来的更好

unsigned int v = 42;
unsigned int r = 0;
unsigned int b;
for (b = sizeof(v) << 2; b; b = b >> 1)
{
    if (v >> b)
    {
        v = v >> b;
        r += b;
    }
}
无符号整数v=42;
无符号整数r=0;
无符号整数b;
对于(b=sizeof(v)>1)
{
如果(v>>b)
{
v=v>>b;
r+=b;
}
}
注意:
b=sizeof(v)这里有一个非常紧凑和快速的扩展,不使用额外的临时变量:

r = 0;

/* If its wider than 32 bits, then we already know that log >= 32.
So store it in R.  */
if (v >> 32)
  {
    r = 32;
    v >>= 32;
  }

/* Now do the exact same thing as the 32 bit algorithm,
except we ADD to R this time.  */
if (tt = v >> 16)
  {
    r += (t = tt >> 8) ? 24 + LogTable256[t] : 16 + LogTable256[tt];
  }
else
  {
    r += (t = v >> 8) ? 8 + LogTable256[t] : LogTable256[v];
  }
这里是一个用
if
s链构建的,同样不使用额外的临时变量。也许不是最快的

  if (tt = v >> 48)
    {
      r = (t = tt >> 8) ? 56 + LogTable256[t] : 48 + LogTable256[tt];
    }
  else if (tt = v >> 32)
    {
      r = (t = tt >> 8) ? 40 + LogTable256[t] : 32 + LogTable256[tt];
    }
  else if (tt = v >> 16)
    {
      r = (t = tt >> 8) ? 24 + LogTable256[t] : 16 + LogTable256[tt];
    }
  else 
    {
      r = (t = v >> 8) ? 8 + LogTable256[t] : LogTable256[v];
    }

如果您使用的是GCC,则在这种情况下不需要查找表

GCC提供了一个内置函数来确定前导零的数量:

内置功能:
返回x中前导0位的数目,从最高有效位位置开始。如果x为0,则结果未定义

因此,您可以定义:

#define LOG2(X) ((unsigned) (8*sizeof (unsigned long long) - __builtin_clzll((X)) - 1))
它适用于任何无符号long-long-int。结果向下舍入

对于x86和AMD64,GCC将其编译为指令,因此解决方案非常快(比查找表快得多)

:

#包括
#定义LOG2(X)((无符号)(8*sizeof(无符号长)-内置clzll((X))-1))
内部主(空){
无符号长输入;
while(scanf(“%llu”,&input)==1){
printf(“日志(%llu)=%u\n”,输入,日志2(输入));
}
返回0;
}

编译输出:

内部函数速度非常快,但对于真正跨平台、独立于编译器的log2实现来说仍然不够。所以,如果有人感兴趣的话,这是我在自己研究这个主题时遇到的最快的、无分支的、类似于CPU抽象Debrijn的算法

const int tab64[64] = {
    63,  0, 58,  1, 59, 47, 53,  2,
    60, 39, 48, 27, 54, 33, 42,  3,
    61, 51, 37, 40, 49, 18, 28, 20,
    55, 30, 34, 11, 43, 14, 22,  4,
    62, 57, 46, 52, 38, 26, 32, 41,
    50, 36, 17, 19, 29, 10, 13, 21,
    56, 45, 25, 31, 35, 16,  9, 12,
    44, 24, 15,  8, 23,  7,  6,  5};

int log2_64 (uint64_t value)
{
    value |= value >> 1;
    value |= value >> 2;
    value |= value >> 4;
    value |= value >> 8;
    value |= value >> 16;
    value |= value >> 32;
    return tab64[((uint64_t)((value - (value >> 1))*0x07EDD5E59A4E28C2)) >> 58];
}
舍入到下一个较低的2次方的部分是从中获取的,获取尾随零的数量的部分是从中获取的(从
(bb&-bb)
代码中挑出设置为1的最右边的位,这在我们将值舍入到下一个2次方后是不需要的)

顺便说一下,32位的实现是

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

int log2_32 (uint32_t value)
{
    value |= value >> 1;
    value |= value >> 2;
    value |= value >> 4;
    value |= value >> 8;
    value |= value >> 16;
    return tab32[(uint32_t)(value*0x07C4ACDD) >> 27];
}
与任何其他计算方法一样,log2要求输入值大于零。

我试图通过强制使用幻数将其转换为64位。不用说,这需要一段时间

然后我找到了德斯蒙德的答案,并决定尝试他的神奇数字作为起点。因为我有一个6核处理器,所以我以0x07EDD5E59A4E28C2/6倍数并行运行它。我很惊讶它马上发现了什么。结果显示0x07EDD5E59A4E28C2/2工作正常

下面是0x07EDD5E59A4E28C2的代码,它为您节省了移位和减法:

int LogBase2(uint64_t n)
{
    static const int table[64] = {
        0, 58, 1, 59, 47, 53, 2, 60, 39, 48, 27, 54, 33, 42, 3, 61,
        51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22, 4, 62,
        57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21, 56,
        45, 25, 31, 35, 16, 9, 12, 44, 24, 15, 8, 23, 7, 6, 5, 63 };

    n |= n >> 1;
    n |= n >> 2;
    n |= n >> 4;
    n |= n >> 8;
    n |= n >> 16;
    n |= n >> 32;

    return table[(n * 0x03f6eaf2cd271461) >> 58];
}
基2整数对数 下面是我对64位无符号整数所做的。这将计算以2为底的对数的下限,它相当于最高有效位的索引。这种方法对于大量数据来说非常快,因为它使用了一个总是在日志中执行的展开循环₂64=6个步骤

本质上,它所做的是减去序列{0]中逐渐变小的正方形≤ K≤ 5:2^(2^k)}={2²,2М⁶, 2.⁸, 2.⁴, 2²,2ª}={429496729665536256,16,4,2,1}并将减去的值的指数k求和

int uint64_log2(uint64_t n)
{
  #define S(k) if (n >= (UINT64_C(1) << k)) { i += k; n >>= k; }

  int i = -(n == 0); S(32); S(16); S(8); S(4); S(2); S(1); return i;

  #undef S
}
拿这个来说:

typedef无符号整数uint;
类型定义uint64_t ulong;
单位为单位(常数浮点x){
返回*(uint*)&x;
}
ulong as_ulong(常数双x){
返回*(ulong*)&x;
}
单元日志2\u快速(常数x){
返回(uint)((as_uint((float)x)>>23)-127);
}
uint log2_fast(施工图x){
返回(uint)((as_ulong((double)x)>>52)-1023);
}
工作原理: 输入整数
x
被转换为
float
,然后重新解释为位。IEEE
float
格式将30-23位的指数存储为偏置127的整数,因此通过将其向右移位23位并减去偏置,我们得到log2(x)。
对于64位整数输入,
x
被强制转换为
double
,其指数位于位62-52(向右移位52位),指数偏差为1023。

这里有一个稍微修改过的(详情请参阅帖子)


旁注:一些用户指出,在某些情况下编译时,可能会导致错误的答案。

< P>如果你正在寻找C++答案,你就到这里,因为它归结为计数零点,然后你得到了根据GordBo.Org调用的代码<代码> BSR 。
std::countl_zero
可从C++20获得,您可能需要将
-std=gnu++2a
添加到您的编译器命令行中

@dbaupp我有一大堆
如果
有各种可能的种类、种类和味道,哪些会做得最好?这只是一个学术问题,对吗?否则,只需使用
\u BitScanReverse64
(msvc)或
\u builtin\u clzll
(gcc)像您已经拥有的那样。(使用最简单的扩展,它看起来像
if(tt=v>>48){…}或者if(tt=v>>32){…}…
,尽管这会比肯德尔正确建议的二进制搜索稍差。)@harold:不,这根本不是一个学术问题。
double ff=(double)(v|1);
return ((*(1+(uint32_t *)&ff))>>52)-1023;  // assumes x86 endianness