关于算法:在C中以整数查找最高设置位(msb)的最快/最有效方法是什么?

What is the fastest/most efficient way to find the highest set bit (msb) in an integer in C?

如果我有一个整数n,我想知道最有效位的位置(也就是说,如果最无效位在右边,我想知道最左边位的位置是1),那么找出最快/最有效的方法是什么?

我知道posix在strings.h中支持ffs()方法来查找第一个set位,但似乎没有相应的fls()方法。

有没有什么明显的方法可以做到这一点?

在不能使用POSIX函数实现可移植性的情况下怎么办?

编辑:一个同时在32位和64位体系结构上工作的解决方案怎么样(许多代码清单似乎只在32位ints上工作)。


海湾合作委员会有:

1
2
3
4
5
6
7
8
9
10
11
 -- Built-in Function: int __builtin_clz (unsigned int x)
     Returns the number of leading 0-bits in X, starting at the most
     significant bit position.  If X is 0, the result is undefined.

 -- Built-in Function: int __builtin_clzl (unsigned long)
     Similar to `__builtin_clz', except the argument type is `unsigned
     long'
.

 -- Built-in Function: int __builtin_clzll (unsigned long long)
     Similar to `__builtin_clz', except the argument type is `unsigned
     long long'
.

我希望它们能被转换成对您当前平台相当有效的东西,无论是那些花哨的比特旋转算法,还是一条指令。

如果您的输入可以为零,一个有用的技巧是__builtin_clz(x | 1):无条件地设置低位而不修改任何其他位,使输出0用于x=0,而不更改任何其他输入的输出。

为了避免需要这样做,您的另一个选择是特定于平台的内部机制,比如ARMGCC的__clz(不需要头),或者支持lzcnt指令的CPU上的x86的_lzcnt_u32。(请注意,lzcnt在较旧的CPU上解码为bsr,而不是错误,后者为非零输入提供31 Lzcnt。)

不幸的是,在非x86平台上,没有办法可以移植地利用各种CLZ指令,这些指令确实将input=0的结果定义为32或64(根据操作数宽度)。x86的lzcnt也会这样做,而bsr生成一个位索引,除非使用31-__builtin_clz(x),否则编译器必须翻转该索引。

(未定义的结果不是C未定义的行为,只是一个未定义的值。它实际上是在指令运行时目标寄存器中的任何内容。AMD记录了这一点,英特尔没有,但英特尔的CPU确实实现了这一行为。但这并不是您要分配给的C变量中以前的内容,当GCC将C转换为ASM时,这通常不是事情的工作方式。另请参见为什么打破LZCNT的"输出依赖关系"很重要?)


假设您使用的是X86和游戏中的一些内嵌汇编程序,Intel将提供一条BSR指令("位扫描反向")。它在一些X86S上很快(在其他X86S上微编码)。从手册中:

Searches the source operand for the most significant set
bit (1 bit). If a most significant 1
bit is found, its bit index is stored
in the destination operand. The source operand can be a
register or a memory location; the
destination operand is a register. The
bit index is an unsigned offset from
bit 0 of the source operand. If the
content source operand is 0, the
content of the destination operand is
undefined.

(如果您在PowerPC上,有一个类似的cntlz指令("计数前导零")。

GCC示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

int main (int,char**)
{
  int n=1;
  for (;;++n) {
    int msb;
    asm("bsrl %1,%0" :"=r"(msb) :"r"(n));
    std::cout << n <<" :" << msb << std::endl;
  }
  return 0;
}

另请参见本内联汇编教程,其中显示(第9.4节)它比循环代码快得多。


因为2^n是一个只有第n位集(1<

http://graphics.stanford.edu/~seander/bithacks.html integerlogobvious

1
2
3
4
5
6
unsigned int v;
unsigned r = 0;

while (v >>= 1) {
    r++;
}

这个"明显"的算法可能对每个人都不是透明的,但是当您意识到代码反复右移一位,直到最左边的位被移走(注意,C将任何非零值视为真),并返回移位的数量时,这是完全合理的。它还意味着即使设置了多个位,它也能工作——结果总是针对最重要的位。

如果向下滚动该页面,则会出现更快、更复杂的变化。但是,如果你知道你处理的数字有很多前导零,那么简单的方法可能提供了可以接受的速度,因为在C语言中位移动相当快,而且简单的算法不需要索引数组。

注意:当使用64位值时,要非常谨慎地使用非常聪明的算法;其中许多算法只适用于32位值。


这应该是闪电般的快:

1
2
3
4
5
6
7
8
9
10
11
12
int msb(unsigned int v) {
  static const int pos[32] = {0, 1, 28, 2, 29, 14, 24, 3,
    30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19,
    16, 7, 26, 12, 18, 6, 11, 5, 10, 9};
  v |= v >> 1;
  v |= v >> 2;
  v |= v >> 4;
  v |= v >> 8;
  v |= v >> 16;
  v = (v >> 1) + 1;
  return pos[(v * 0x077CB531UL) >> 27];
}


这有点像找到一种整数日志。有一些小把戏,但我已经为这个做了自己的工具。当然,目标是提高速度。

我的实现是CPU已经有了一个自动的位检测器,用于整数到浮点的转换!所以用那个。

1
2
double ff=(double)(v|1);
return ((*(1+(uint32_t *)&ff))>>20)-1023;  // assumes x86 endianness

此版本将值强制转换为一个双精度数,然后读取指数,该指数告诉您位的位置。花式移位和减法是从IEEE值中提取适当的部分。

使用float的速度稍微快一点,但是由于它的精度较小,float只能给出前24位的位置。

为了安全地做到这一点,在C++或C中没有未定义的行为,使用EDCOX1 OR 0表示,而不是为类型双关进行指针转换。编译器知道如何高效地内联它。

1
2
3
4
5
6
7
8
// static_assert(sizeof(double) == 2 * sizeof(uint32_t),"double isn't 8-byte IEEE binary64");
// and also static_assert something about FLT_ENDIAN?

double ff=(double)(v|1);

uint32_t tmp;
memcpy(&tmp, ((const char*)&ff)+sizeof(uint32_t), sizeof(uint32_t));
return (tmp>>20)-1023;

或者在C99及更高版本中,使用union {double d; uint32_t u[2];};。但是请注意,在C++中,联合类型双关只在一些编译器上作为扩展而不是在ISO C++中被支持。

对于前导零计数指令,这通常比平台特定的内部函数慢,但便携式ISO C没有这样的功能。一些CPU还缺少前导零计数指令,但其中一些CPU可以有效地将整数转换为double。但是,将fp位模式归位为integer的类型可能很慢(例如,在PowerPC上,它需要存储/重新加载,通常会导致加载命中存储暂停)。

这种算法可能对SIMD实现有用,因为拥有SIMD lzcnt的CPU更少。X86仅使用AVX512CD获得这样的指令


这里是Kaz Kylheku

我为超过63位的数字(gcc x86_64上的long-long类型)确定了两种方法的基准,远离符号位。

(我碰巧需要这个"找到最高点"的东西,你看。)

我实现了数据驱动的二进制搜索(紧密地基于上面的一个答案)。我还手工实现了一个完全展开的决策树,它只是带有直接操作数的代码。没有循环,没有表。

除了二进制搜索有显式测试的n=0情况外,决策树(最高u位展开)的基准测试速度快69%。

二进制搜索对0案例的特殊测试只比没有特殊测试的决策树快48%。

编译器,机器:(GCC 4.5.2,-O3,x86-64,2867 MHz Intel Core i5)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
int highest_bit_unrolled(long long n)
{
  if (n & 0x7FFFFFFF00000000) {
    if (n & 0x7FFF000000000000) {
      if (n & 0x7F00000000000000) {
        if (n & 0x7000000000000000) {
          if (n & 0x4000000000000000)
            return 63;
          else
            return (n & 0x2000000000000000) ? 62 : 61;
        } else {
          if (n & 0x0C00000000000000)
            return (n & 0x0800000000000000) ? 60 : 59;
          else
            return (n & 0x0200000000000000) ? 58 : 57;
        }
      } else {
        if (n & 0x00F0000000000000) {
          if (n & 0x00C0000000000000)
            return (n & 0x0080000000000000) ? 56 : 55;
          else
            return (n & 0x0020000000000000) ? 54 : 53;
        } else {
          if (n & 0x000C000000000000)
            return (n & 0x0008000000000000) ? 52 : 51;
          else
            return (n & 0x0002000000000000) ? 50 : 49;
        }
      }
    } else {
      if (n & 0x0000FF0000000000) {
        if (n & 0x0000F00000000000) {
          if (n & 0x0000C00000000000)
            return (n & 0x0000800000000000) ? 48 : 47;
          else
            return (n & 0x0000200000000000) ? 46 : 45;
        } else {
          if (n & 0x00000C0000000000)
            return (n & 0x0000080000000000) ? 44 : 43;
          else
            return (n & 0x0000020000000000) ? 42 : 41;
        }
      } else {
        if (n & 0x000000F000000000) {
          if (n & 0x000000C000000000)
            return (n & 0x0000008000000000) ? 40 : 39;
          else
            return (n & 0x0000002000000000) ? 38 : 37;
        } else {
          if (n & 0x0000000C00000000)
            return (n & 0x0000000800000000) ? 36 : 35;
          else
            return (n & 0x0000000200000000) ? 34 : 33;
        }
      }
    }
  } else {
    if (n & 0x00000000FFFF0000) {
      if (n & 0x00000000FF000000) {
        if (n & 0x00000000F0000000) {
          if (n & 0x00000000C0000000)
            return (n & 0x0000000080000000) ? 32 : 31;
          else
            return (n & 0x0000000020000000) ? 30 : 29;
        } else {
          if (n & 0x000000000C000000)
            return (n & 0x0000000008000000) ? 28 : 27;
          else
            return (n & 0x0000000002000000) ? 26 : 25;
        }
      } else {
        if (n & 0x0000000000F00000) {
          if (n & 0x0000000000C00000)
            return (n & 0x0000000000800000) ? 24 : 23;
          else
            return (n & 0x0000000000200000) ? 22 : 21;
        } else {
          if (n & 0x00000000000C0000)
            return (n & 0x0000000000080000) ? 20 : 19;
          else
            return (n & 0x0000000000020000) ? 18 : 17;
        }
      }
    } else {
      if (n & 0x000000000000FF00) {
        if (n & 0x000000000000F000) {
          if (n & 0x000000000000C000)
            return (n & 0x0000000000008000) ? 16 : 15;
          else
            return (n & 0x0000000000002000) ? 14 : 13;
        } else {
          if (n & 0x0000000000000C00)
            return (n & 0x0000000000000800) ? 12 : 11;
          else
            return (n & 0x0000000000000200) ? 10 : 9;
        }
      } else {
        if (n & 0x00000000000000F0) {
          if (n & 0x00000000000000C0)
            return (n & 0x0000000000000080) ? 8 : 7;
          else
            return (n & 0x0000000000000020) ? 6 : 5;
        } else {
          if (n & 0x000000000000000C)
            return (n & 0x0000000000000008) ? 4 : 3;
          else
            return (n & 0x0000000000000002) ? 2 : (n ? 1 : 0);
        }
      }
    }
  }
}

int highest_bit(long long n)
{
  const long long mask[] = {
    0x000000007FFFFFFF,
    0x000000000000FFFF,
    0x00000000000000FF,
    0x000000000000000F,
    0x0000000000000003,
    0x0000000000000001
  };
  int hi = 64;
  int lo = 0;
  int i = 0;

  if (n == 0)
    return 0;

  for (i = 0; i < sizeof mask / sizeof mask[0]; i++) {
    int mi = lo + (hi - lo) / 2;

    if ((n >> mi) != 0)
      lo = mi;
    else if ((n & (mask[i] << lo)) != 0)
      hi = mi;
  }

  return lo + 1;
}

快速脏测试程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int highest_bit_unrolled(long long n);
int highest_bit(long long n);

main(int argc, char **argv)
{
  long long n = strtoull(argv[1], NULL, 0);
  int b1, b2;
  long i;
  clock_t start = clock(), mid, end;

  for (i = 0; i < 1000000000; i++)
    b1 = highest_bit_unrolled(n);

  mid = clock();

  for (i = 0; i < 1000000000; i++)
    b2 = highest_bit(n);

  end = clock();

  printf("highest bit of 0x%llx/%lld = %d, %d
"
, n, n, b1, b2);

  printf("time1 = %d
"
, (int) (mid - start));
  printf("time2 = %d
"
, (int) (end - mid));
  return 0;
}

只使用-o2,差别就变大了。决策树几乎快了四倍。

我还以天真的比特移位代码为基准:

1
2
3
4
5
6
7
int highest_bit_shift(long long n)
{
  int i = 0;
  for (; n; n >>= 1, i++)
    ; /* empty */
  return i;
}

正如人们所期望的,这对于小数字来说是很快的。在确定n==1的最高位是1时,它的基准测试速度快了80%以上。然而,在63位空间中随机选择的数字的一半具有63位集!

在输入0x3ffffffffffffffffff时,决策树版本比它在1上的速度快得多,并且显示比比特移位器快1120%(12.2倍)。

我还将把决策树与GCC内建进行基准测试,并尝试混合输入,而不是重复相同的数字。可能有一些坚持的分支预测正在进行,也可能有一些不切实际的缓存场景,这使得它在重复时人为地更快。


怎么样

1
2
3
4
5
int highest_bit(unsigned int a) {
    int count;
    std::frexp(a, &count);
    return count - 1;
}


尽管我可能只会在绝对需要最佳性能(例如,对于编写涉及bitboard的某种棋盘游戏ai)的情况下使用此方法,但最有效的解决方案是使用内联asm。有关带解释的代码,请参阅此博客文章的"优化"部分。

[...], the bsrl assembly instruction computes the position of the most significant bit. Thus, we could use this asm statement:

1
2
3
asm ("bsrl %1, %0"
     :"=r" (position)
     :"r" (number));


下面是一些(简单的)基准,目前在这一页上给出的算法…

算法还没有对无符号int的所有输入进行过测试;因此,在盲目使用某些东西之前,首先要检查这一点;)

在我的机器上,CLZ(内置)和ASM工作得最好。ASM似乎比CLZ更快…但这可能是由于简单的基准…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
//////// go.c ///////////////////////////////
// compile with:  gcc go.c -o go -lm
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/***************** math ********************/

#define POS_OF_HIGHESTBITmath(a) /* 0th position is the Least-Signif-Bit */    \
  ((unsigned) log2(a))         /* thus: do not use if a <= 0 */  


#define NUM_OF_HIGHESTBITmath(a) ((a)               \
                  ? (1U << POS_OF_HIGHESTBITmath(a))    \
                  : 0)




/***************** clz ********************/

unsigned NUM_BITS_U = ((sizeof(unsigned) << 3) - 1);
#define POS_OF_HIGHESTBITclz(a) (NUM_BITS_U - __builtin_clz(a)) /* only works for a != 0 */

#define NUM_OF_HIGHESTBITclz(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITclz(a))  \
                 : 0)



/***************** i2f ********************/

double FF;
#define POS_OF_HIGHESTBITi2f(a) (FF = (double)(ui|1), ((*(1+(unsigned*)&FF))>>20)-1023)


#define NUM_OF_HIGHESTBITi2f(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITi2f(a))  \
                 : 0)





/***************** asm ********************/

unsigned OUT;
#define POS_OF_HIGHESTBITasm(a) (({asm("bsrl %1,%0" :"=r"(OUT) :"r"(a));}), OUT)

#define NUM_OF_HIGHESTBITasm(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITasm(a))  \
                 : 0)





/***************** bitshift1 ********************/

#define NUM_OF_HIGHESTBITbitshift1(a) (({   \
  OUT = a;                  \
  OUT |= (OUT >> 1);                \
  OUT |= (OUT >> 2);                \
  OUT |= (OUT >> 4);                \
  OUT |= (OUT >> 8);                \
  OUT |= (OUT >> 16);               \
      }), (OUT & ~(OUT >> 1)))          \



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

#define POS_OF_HIGHESTBITbitshift2(a) (({   \
  OUT = a;                  \
  OUT |= OUT >> 1;              \
  OUT |= OUT >> 2;              \
  OUT |= OUT >> 4;              \
  OUT |= OUT >> 8;              \
  OUT |= OUT >> 16;             \
  OUT = (OUT >> 1) + 1;             \
      }), POS[(OUT * 0x077CB531UL) >> 27])


#define NUM_OF_HIGHESTBITbitshift2(a) ((a)              \
                       ? (1U << POS_OF_HIGHESTBITbitshift2(a)) \
                       : 0)




#define LOOPS 100000000U

int main()
{
  time_t start, end;
  unsigned ui;
  unsigned n;

  /********* Checking the first few unsigned values (you'll need to check all if you want to use an algorithm here) **************/
  printf("math
"
);
  for (ui = 0U; ui < 18; ++ui)
    printf("%i\t%i
"
, ui, NUM_OF_HIGHESTBITmath(ui));

  printf("

"
);

  printf("clz
"
);
  for (ui = 0U; ui < 18U; ++ui)
    printf("%i\t%i
"
, ui, NUM_OF_HIGHESTBITclz(ui));

  printf("

"
);

  printf("i2f
"
);
  for (ui = 0U; ui < 18U; ++ui)
    printf("%i\t%i
"
, ui, NUM_OF_HIGHESTBITi2f(ui));

  printf("

"
);

  printf("asm
"
);
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i
"
, ui, NUM_OF_HIGHESTBITasm(ui));
  }

  printf("

"
);

  printf("bitshift1
"
);
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i
"
, ui, NUM_OF_HIGHESTBITbitshift1(ui));
  }

  printf("

"
);

  printf("bitshift2
"
);
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i
"
, ui, NUM_OF_HIGHESTBITbitshift2(ui));
  }

  printf("

Please wait...

"
);


  /************************* Simple clock() benchmark ******************/
  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITmath(ui);
  end = clock();
  printf("math:\t%e
"
, (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITclz(ui);
  end = clock();
  printf("clz:\t%e
"
, (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITi2f(ui);
  end = clock();
  printf("i2f:\t%e
"
, (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITasm(ui);
  end = clock();
  printf("asm:\t%e
"
, (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITbitshift1(ui);
  end = clock();
  printf("bitshift1:\t%e
"
, (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITbitshift2(ui);
  end = clock();
  printf("bitshift2\t%e
"
, (double)(end-start)/CLOCKS_PER_SEC);

  printf("
The lower, the better. Take note that a negative exponent is good! ;)
"
);

  return EXIT_SUCCESS;
}


1
2
3
4
5
6
7
8
9
10
unsigned int
msb32(register unsigned int x)
{
        x |= (x >> 1);
        x |= (x >> 2);
        x |= (x >> 4);
        x |= (x >> 8);
        x |= (x >> 16);
        return(x & ~(x >> 1));
}

1个寄存器,13个指令。信不信由你,这通常比上面提到的在线性时间内运行的BSR指令快。这是对数时间。

从http://aggregate.org/magic/most%20significant%201%20bit


我需要一个例行程序来完成这项工作,在搜索网页(并找到这个页面)之前,我想出了一个基于二进制搜索的解决方案。尽管我相信以前有人做过这件事!它在一个固定的时间内运行,并且可以比发布的"明显"的解决方案更快,尽管我没有提出任何伟大的主张,只是出于兴趣发布它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int highest_bit(unsigned int a) {
  static const unsigned int maskv[] = { 0xffff, 0xff, 0xf, 0x3, 0x1 };
  const unsigned int *mask = maskv;
  int l, h;

  if (a == 0) return -1;

  l = 0;
  h = 32;

  do {
    int m = l + (h - l) / 2;

    if ((a >> m) != 0) l = m;
    else if ((a & (*mask << l)) != 0) h = m;

    mask++;
  } while (l < h - 1);

  return l;
}

这里有些过于复杂的答案。只有当输入已经是2的幂次时,才能使用清创技术,否则会有更好的方法。对于2输入的功率,Debluin绝对是最快的,甚至比我测试过的任何处理器上的_BitScanReverse更快。然而,在一般情况下,EDOCX1(或编译器中调用的任何内部函数)是最快的(在某些CPU上,它可以是微编码的)。

如果内在函数不是一个选项,这里是处理一般输入的最佳软件解决方案。

1
2
3
4
5
6
7
8
9
u8  inline log2 (u32 val)  {
    u8  k = 0;
    if (val > 0x0000FFFFu) { val >>= 16; k  = 16; }
    if (val > 0x000000FFu) { val >>= 8;  k |= 8;  }
    if (val > 0x0000000Fu) { val >>= 4;  k |= 4;  }
    if (val > 0x00000003u) { val >>= 2;  k |= 2;  }
    k |= (val & 2) >> 1;
    return k;
}

请注意,与大多数其他答案不同,此版本不需要在末尾进行清创查找。它计算位置。

表是更好的选择,但是如果您重复调用足够多次,缓存丢失的风险会因表的加速而消失。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
u8 kTableLog2[256] = {
0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};

u8 log2_table(u32 val)  {
    u8  k = 0;
    if (val > 0x0000FFFFuL) { val >>= 16; k  = 16; }
    if (val > 0x000000FFuL) { val >>=  8; k |=  8; }
    k |= kTableLog2[val]; // precompute the Log2 of the low byte

    return k;
}

这将产生这里给出的任何软件答案的最高吞吐量,但是如果您只是偶尔调用它,那么您更喜欢像我的第一个片段那样的无表解决方案。


c99给了我们log2。这就不再需要您在本页上看到的所有特殊的sauce log2实现。您可以这样使用标准的log2实现:

1
2
3
4
5
const auto n = 13UL;
const auto Index = (unsigned long)log2(n);

printf("MSB is: %u
"
, Index); // Prints 3 (zero offset)

需要对0ULn进行防护,因为:

-∞ is returned and FE_DIVBYZERO is raised

我写了一个例子,在这里随意地将Index设置为ULONG_MAX:https://ideone.com/u26vsi

ephemient的gcc唯一答案的Visual Studio推论是:

1
2
3
4
5
6
const auto n = 13UL;
unsigned long Index;

_BitScanReverse(&Index, n);
printf("MSB is: %u
"
, Index); // Prints 3 (zero offset)

_BitScanReverse文件规定Index为:

Loaded with the bit position of the first set bit (1) found

在实践中,我发现如果n0UL,那么Index被设置为0UL,就像1ULn一样。但在文件中,对于0ULn,唯一保证的是:

0 if no set bits were found

因此,与上面的首选log2实现类似,在这种情况下,应检查返回设置Index为标记值。我在这里再次编写了一个使用ULONG_MAX作为这个标志值的示例:http://rexteter.com/gcu61409


使用逐次逼近的C语言版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned int getMsb(unsigned int n)
{
  unsigned int msb  = sizeof(n) * 4;
  unsigned int step = msb;
  while (step > 1)
 {
    step /=2;
    if (n>>msb)
     msb += step;
   else
     msb -= step;
 }
  if (n>>msb)
    msb++;
  return (msb - 1);
}

优点:无论提供多少个循环,运行时间都是恒定的,因为循环的数目总是相同的。(使用"unsigned int"时有4个循环)


正如上面的答案所指出的,有许多方法可以确定最重要的位。然而,正如前面指出的,这些方法对于32位或64位寄存器可能是唯一的。edu bithacks页面提供了适用于32位和64位计算的解决方案。只需一点工作,它们就可以组合起来,为获得MSB提供一种可靠的跨体系结构方法。我在64位和32位计算机上编译/工作得到的解决方案是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#if defined(__LP64__) || defined(_LP64)
# define BUILD_64   1
#endif

#include <stdio.h>
#include <stdint.h>  /* for uint32_t */

/* CHAR_BIT  (or include limits.h) */
#ifndef CHAR_BIT
#define CHAR_BIT  8
#endif  /* CHAR_BIT */

/*
 * Find the log base 2 of an integer with the MSB N set in O(N)
 * operations. (on 64bit & 32bit architectures)
 */

int
getmsb (uint32_t word)
{
    int r = 0;
    if (word < 1)
        return 0;
#ifdef BUILD_64
    union { uint32_t u[2]; double d; } t;  // temp
    t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] = 0x43300000;
    t.u[__FLOAT_WORD_ORDER!=LITTLE_ENDIAN] = word;
    t.d -= 4503599627370496.0;
    r = (t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] >> 20) - 0x3FF;
#else
    while (word >>= 1)
    {
        r++;
    }
#endif  /* BUILD_64 */
    return r;
}


这是某种二进制搜索,它适用于所有类型的(无符号!)整数类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <climits>
#define UINT (unsigned int)
#define UINT_BIT (CHAR_BIT*sizeof(UINT))

int msb(UINT x)
{
    if(0 == x)
        return -1;

    int c = 0;

    for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
    if(static_cast<UINT>(x >> i))
    {
        x >>= i;
        c |= i;
    }

    return c;
}

要完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <climits>
#define UINT unsigned int
#define UINT_BIT (CHAR_BIT*sizeof(UINT))

int lsb(UINT x)
{
    if(0 == x)
        return -1;

    int c = UINT_BIT-1;

    for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
    if(static_cast<UINT>(x << i))
    {
        x <<= i;
        c ^= i;
    }

    return c;
}


思考位运算符。

我第一次误解了这个问题。您应该使用最左边的位集(其他的零)生成一个int。假设CMP设置为该值:

1
2
3
4
5
position = sizeof(int)*8
while(!(n & cmp)){
   n <<=1;
   position--;
}


在Josh的基准上扩展…我们可以改进CLZ如下

1
2
3
4
5
/***************** clz2 ********************/

#define NUM_OF_HIGHESTBITclz2(a) ((a)                              \
                  ? (((1U) << (sizeof(unsigned)*8-1)) >> __builtin_clz(a)) \
                  : 0)

关于ASM:请注意,有BSR和BSRL(这是"长"版本)。正常情况下可能会快一点。


既然这是"又一个"方法,那么把它放进去,似乎与其他已经给出的方法不同。

如果x==0返回-1,否则返回floor( log2(x))(最大结果31)

从32位减少到4位,然后使用表。也许不雅,但实用。

这是我在不想使用__builtin_clz时使用的,因为可移植性问题。

为了使它更紧凑,可以使用循环来减少,每次增加4到R,最多7次迭代。或者一些混合,比如(64位):循环减少到8,测试减少到4。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int log2floor( unsigned x ){
   static const signed char wtab[16] = {-1,0,1,1, 2,2,2,2, 3,3,3,3,3,3,3,3};
   int r = 0;
   unsigned xk = x >> 16;
   if( xk != 0 ){
       r = 16;
       x = xk;
   }
   // x is 0 .. 0xFFFF
   xk = x >> 8;
   if( xk != 0){
       r += 8;
       x = xk;
   }
   // x is 0 .. 0xFF
   xk = x >> 4;
   if( xk != 0){
       r += 4;
       x = xk;
   }
   // now x is 0..15; x=0 only if originally zero.
   return r + wtab[x];
}


我知道这个问题很古老,但是我自己刚实现了一个msb()函数,我发现,这里和其他网站上提供的大多数解决方案并不一定是最有效的——至少对于我个人对效率的定义来说是如此(另请参阅下面的更新)。这就是为什么:

大多数解决方案(尤其是那些采用某种二进制搜索方案或NA的方案)?ve方法从右到左进行线性扫描)似乎忽略了一个事实,即对于任意二进制数,没有多少是以非常长的零序列开始的。实际上,对于任何比特宽度,所有整数的一半以1开头,四分之一以01开头。看到我要去哪里了吗?我的论点是,从最重要的位位置到最不重要的位(从左到右)的线性扫描并不像第一眼看到的那样"线性"。

可以看出1,对于任何比特宽度,需要测试的平均比特数最多为2。这就转化为o(1)相对于比特数(!)的摊余时间复杂性。.

当然,最坏的情况仍然是O(n),比使用类似二进制搜索的方法得到的O(log(n))更糟,但是由于最坏的情况太少,所以对于大多数应用程序来说,它们是可以忽略的(更新:不完全是:可能很少,但它们可能发生的概率很高-请参阅下面的更新)。

这是"NA"?我已经想出了这样的方法,至少在我的机器上打败了大多数其他方法(32位int的二进制搜索方案总是要求log<2)/Sub >(32)=5步,而这个愚蠢的算法平均需要小于2)-抱歉这是C++而不是纯C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <typename T>
auto msb(T n) -> int
{
    static_assert(std::is_integral<T>::value && !std::is_signed<T>::value,
       "msb<T>(): T must be an unsigned integral type.");

    for (T i = std::numeric_limits<T>::digits - 1, mask = 1 << i; i >= 0; --i, mask >>= 1)
    {
        if ((n & mask) != 0)
            return i;
    }

    return 0;
}

更新:虽然我在这里所写的对于任意整数都是完全正确的,其中每一个位的组合都是同样可能的(我的速度测试只是测量了为所有32位整数确定msb所花的时间),但对于将调用此类函数的实存整数,通常遵循不同的模式:例如,在我的代码中,这个函数用于确定对象大小是2的幂,或查找大于或等于对象大小的下一个2的幂。我的猜测是,大多数使用MSB的应用程序所涉及的数字远小于整数所能表示的最大数字(对象大小很少使用大小t中的所有位)。在这种情况下,我的解决方案实际上会比二进制搜索方法执行得更差,因此后者可能是首选的,即使我的解决方案在所有整数中循环的速度更快。
tl;dr:现实生活中的整数可能会偏向于这种简单算法的最坏情况,这会使它最终的性能更差——尽管它对于真正任意的整数已经摊销了o(1)。

1理由如下(草稿):设n为位数(位宽度)。共有2n个整数,可以用n位表示。有2n-1个以1开头的整数(第一个1是固定的,其余的n-1位可以是任何值)。这些整数只需要循环的一个交互作用来确定MSB。此外,还有2n-2个以01开头的整数,需要2个迭代,2n-3个以001开头的整数,需要3个迭代,等等。

如果我们对所有可能整数的所有必要迭代求和,并除以整数总数2n,我们得到确定n位整数的msb所需的平均迭代次数:

(1*2N-1+2*2N-2+3*2N-3+…+n)/2n

这一系列的平均迭代实际上是收敛的,对于n向无穷大的方向,其极限为2。

因此,NA?对于任意数量的比特,从左到右的算法实际上具有0(1)的摊余常数时间复杂性。


哇,答案太多了。我对回答一个旧问题并不感到抱歉。

1
2
3
4
5
6
7
8
9
10
11
int result = 0;//could be a char or int8_t instead
if(value){//this assumes the value is 64bit
    if(0xFFFFFFFF00000000&value){  value>>=(1<<5); result|=(1<<5);  }//if it is 32bit then remove this line
    if(0x00000000FFFF0000&value){  value>>=(1<<4); result|=(1<<4);  }//and remove the 32msb
    if(0x000000000000FF00&value){  value>>=(1<<3); result|=(1<<3);  }
    if(0x00000000000000F0&value){  value>>=(1<<2); result|=(1<<2);  }
    if(0x000000000000000C&value){  value>>=(1<<1); result|=(1<<1);  }
    if(0x0000000000000002&value){  result|=(1<<0);  }
}else{
  result=-1;
}

这个答案和另一个答案非常相似…哦,好吧。


注意,您要做的是计算一个整数的整数log2,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>

unsigned int
Log2(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1; int k=0;
    for( step = 1; step < bits; ) {
        n |= (n >> step);
        step *= 2; ++k;
    }
    //printf("%ld %ld
",x, (x - (n >> 1)) );
    return(x - (n >> 1));
}

注意,您可以尝试一次搜索超过1位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
unsigned int
Log2_a(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1;
    int step2 = 0;
    //observe that you can move 8 bits at a time, and there is a pattern...
    //if( x>1<<step2+8 ) { step2+=8;
        //if( x>1<<step2+8 ) { step2+=8;
            //if( x>1<<step2+8 ) { step2+=8;
            //}
        //}
    //}
    for( step2=0; x>1L<<step2+8; ) {
        step2+=8;
    }
    //printf("step2 %d
",step2);
    for( step = 0; x>1L<<(step+step2); ) {
        step+=1;
        //printf("
step %d
",step+step2);
    }
    printf("
log2(%ld) %d
",x,step+step2);
    return(step+step2);
}

这种方法使用二进制搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
unsigned int
Log2_b(unsigned long x)
{
    unsigned long n = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int hbit = bits-1;
    unsigned int lbit = 0;
    unsigned long guess = bits/2;
    int found = 0;

    while ( hbit-lbit>1 ) {
        //printf("log2(%ld) %d<%d<%d
",x,lbit,guess,hbit);
        //when value between guess..lbit
        if( (x<=(1L<<guess)) ) {
           //printf("
%ld < 1<<%d %ld
",x,guess,1L<<guess);
            hbit=guess;
            guess=(hbit+lbit)/2;
            //printf("
log2(%ld) %d<%d<%d
",x,lbit,guess,hbit);
        }
        //when value between hbit..guess
        //else
        if( (x>(1L<<guess)) ) {
            //printf("
%ld > 1<<%d %ld
",x,guess,1L<<guess);
            lbit=guess;
            guess=(hbit+lbit)/2;
            //printf("
log2(%ld) %d<%d<%d
",x,lbit,guess,hbit);
        }
    }
    if( (x>(1L<<guess)) ) ++guess;
    printf("
log2(x%ld)=r%d
",x,guess);
    return(guess);
}

另一种二进制搜索方法,也许更易读,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
unsigned int
Log2_c(unsigned long x)
{
    unsigned long v = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int step = bits;
    unsigned int res = 0;
    for( step = bits/2; step>0; )
    {
        //printf("log2(%ld) v %d >> step %d = %ld
",x,v,step,v>>step);
        while ( v>>step ) {
            v>>=step;
            res+=step;
            //printf("
log2(%ld) step %d res %d v>>step %ld
",x,step,res,v);
        }
        step /= 2;
    }
    if( (x>(1L<<res)) ) ++res;
    printf("
log2(x%ld)=r%ld
",x,res);
    return(res);
}

因为你想测试这些,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
    unsigned long int x = 3;
    for( x=2; x<1000000000; x*=2 ) {
        //printf("x %ld, x+1 %ld, log2(x+1) %d
",x,x+1,Log2(x+1));
        printf("
x %ld, x+1 %ld, log2_a(x+1) %d
",x,x+1,Log2_a(x+1));
        printf("
x %ld, x+1 %ld, log2_b(x+1) %d
",x,x+1,Log2_b(x+1));
        printf("
x %ld, x+1 %ld, log2_c(x+1) %d
",x,x+1,Log2_c(x+1));
    }
    return(0);
}

代码:

1
2
3
4
5
6
7
    // x>=1;
    unsigned func(unsigned x) {
    double d = x ;
    int p= (*reinterpret_cast<long long*>(&d) >> 52) - 1023;
    printf("The left-most non zero bit of %d is bit %d
"
, x, p);
    }

或者通过设置y=1获取fpu指令fyl2x(y*log2 x)的整数部分


我假设您的问题是一个整数(下面称为v),而不是无符号整数。

1
2
3
4
5
6
7
8
9
10
11
int v = 612635685; // whatever value you wish

unsigned int get_msb(int v)
{
    int r = 31;                         // maximum number of iteration until integer has been totally left shifted out, considering that first bit is index 0. Also we could use (sizeof(int)) << 3 - 1 instead of 31 to make it work on any platform.

    while (!(v & 0x8000000) && r--) {   // mask of the highest bit
        v <<= 1;                        // multiply integer by 2.
    }
    return r;                           // will even return -1 if no bit was set, allowing error catch
}

如果您想让它在不考虑符号的情况下工作,可以在循环之前添加一个额外的"v<<=1;"(并相应地将r值更改为30)。如果我忘了什么请告诉我。我还没有测试过,但它应该可以正常工作。


另一个海报使用字节范围的查找提供了一个查找表。如果您希望获得更高的性能(以32K内存而不是256个查找条目为代价),这里有一个使用15位查找表的解决方案,C 7 for.NET。

有趣的部分是初始化表。由于它是我们在进程生命周期中需要的一个相对较小的块,所以我使用Marshal.AllocHGlobal为此分配了非托管内存。如您所见,为了获得最大的性能,整个示例是以本机方式编写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
readonly static byte[] msb_tab_15;

// Initialize a table of 32768 bytes with the bit position (counting from LSB=0)
// of the highest 'set' (non-zero) bit of its corresponding 16-bit index value.
// The table is compressed by half, so use (value >> 1) for indexing.
static MyStaticInit()
{
    var p = new byte[0x8000];

    for (byte n = 0; n < 16; n++)
        for (int c = (1 << n) >> 1, i = 0; i < c; i++)
            p[c + i] = n;

    msb_tab_15 = p;
}

该表需要通过上述代码进行一次性初始化。它是只读的,因此可以共享单个全局副本以进行并发访问。使用此表,您可以快速查找整型日志2,这是我们在这里查找的所有不同整型宽度(8、16、32和64位)的日志。

注意,0的表条目,即"最高设置位"概念未定义的唯一整数,被赋予值-1。这种区别对于正确处理下面代码中的0值大写词是必要的。不需要进一步的ADO,这里是各种整数原语的代码:

ulong(64位)版本

1
2
3
4
5
6
7
8
9
10
/// <summary> Index of the highest set bit in 'v', or -1 for value '0' </summary>
public static int HighestOne(this ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 0x40) - 1;      // handles cases v==0 and MSB==63

    int j = /**/ (int)((0xFFFFFFFFU - v /****/) >> 58) & 0x20;
    j |= /*****/ (int)((0x0000FFFFU - (v >> j)) >> 59) & 0x10;
    return j + msb_tab_15[v >> (j + 1)];
}

uint(32位)版本

1
2
3
4
5
6
7
8
9
/// <summary> Index of the highest set bit in 'v', or -1 for value '0' </summary>
public static int HighestOne(uint v)
{
    if ((int)v <= 0)
        return (int)((v >> 26) & 0x20) - 1;     // handles cases v==0 and MSB==31

    int j = (int)((0x0000FFFFU - v) >> 27) & 0x10;
    return j + msb_tab_15[v >> (j + 1)];
}

上述各项过载

1
2
3
4
5
6
7
public static int HighestOne(long v) => HighestOne((ulong)v);
public static int HighestOne(int v) => HighestOne((uint)v);
public static int HighestOne(ushort v) => msb_tab_15[v >> 1];
public static int HighestOne(short v) => msb_tab_15[(ushort)v >> 1];
public static int HighestOne(char ch) => msb_tab_15[ch >> 1];
public static int HighestOne(sbyte v) => msb_tab_15[(byte)v >> 1];
public static int HighestOne(byte v) => msb_tab_15[v >> 1];

这是一个完整的工作解决方案,它代表了.NET 4.7.2上的最佳性能,对于许多替代方案,我将其与专门的性能测试工具进行了比较。其中一些在下面提到。测试参数是所有65位位置的均匀密度,即0…31/63加上值0(产生结果-1)。随机填充低于目标索引位置的位。测试仅为x64,发布模式,启用了JIT优化。

这里是我正式答案的结尾;下面是一些临时注释和到源代码的链接,这些源代码是与我运行以验证上述代码的性能和正确性的测试相关联的备选测试候选的源代码。

上面提供的版本,编码为tab16a,在很多次跑步中都是一个始终如一的赢家。这些不同的候选者,以活跃的工作/草稿形式,可以在这里、这里和这里找到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
 1  candidates.HighestOne_Tab16A               622,496
 2  candidates.HighestOne_Tab16C               628,234
 3  candidates.HighestOne_Tab8A                649,146
 4  candidates.HighestOne_Tab8B                656,847
 5  candidates.HighestOne_Tab16B               657,147
 6  candidates.HighestOne_Tab16D               659,650
 7  _highest_one_bit_UNMANAGED.HighestOne_U    702,900
 8  de_Bruijn.IndexOfMSB                       709,672
 9  _old_2.HighestOne_Old2                     715,810
10  _test_A.HighestOne8                        757,188
11  _old_1.HighestOne_Old1                     757,925
12  _test_A.HighestOne5  (unsafe)              760,387
13  _test_B.HighestOne8  (unsafe)              763,904
14  _test_A.HighestOne3  (unsafe)              766,433
15  _test_A.HighestOne1  (unsafe)              767,321
16  _test_A.HighestOne4  (unsafe)              771,702
17  _test_B.HighestOne2  (unsafe)              772,136
18  _test_B.HighestOne1  (unsafe)              772,527
19  _test_B.HighestOne3  (unsafe)              774,140
20  _test_A.HighestOne7  (unsafe)              774,581
21  _test_B.HighestOne7  (unsafe)              775,463
22  _test_A.HighestOne2  (unsafe)              776,865
23  candidates.HighestOne_NoTab                777,698
24  _test_B.HighestOne6  (unsafe)              779,481
25  _test_A.HighestOne6  (unsafe)              781,553
26  _test_B.HighestOne4  (unsafe)              785,504
27  _test_B.HighestOne5  (unsafe)              789,797
28  _test_A.HighestOne0  (unsafe)              809,566
29  _test_B.HighestOne0  (unsafe)              814,990
30  _highest_one_bit.HighestOne                824,345
30  _bitarray_ext.RtlFindMostSignificantBit    894,069
31  candidates.HighestOne_Naive                898,865

值得注意的是,ntdll.dll!RtlFindMostSignificantBit通过P/Invoke的糟糕性能:

1
2
[DllImport("ntdll.dll"), SuppressUnmanagedCodeSecurity, SecuritySafeCritical]
public static extern int RtlFindMostSignificantBit(ulong ul);

这真的太糟糕了,因为这是整个实际功能:

1
2
3
4
5
6
    RtlFindMostSignificantBit:
        bsr rdx, rcx  
        mov eax,0FFFFFFFFh  
        movzx ecx, dl  
        cmovne      eax,ecx  
        ret

我无法想象这五条线导致的性能不佳,因此必须归咎于管理/本地转换惩罚。我也感到惊讶的是,测试确实支持32kb(和64kb)的short16位直接查找表,而不是128字节(和256字节)的byte8位查找表。我认为下面的内容与16位查找相比更具竞争力,但后者始终优于这一点:

1
2
3
4
5
6
7
8
9
10
11
public static int HighestOne_Tab8A(ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 64) - 1;

    int j;
    j =  /**/ (int)((0xFFFFFFFFU - v) >> 58) & 32;
    j += /**/ (int)((0x0000FFFFU - (v >> j)) >> 59) & 16;
    j += /**/ (int)((0x000000FFU - (v >> j)) >> 60) & 8;
    return j + msb_tab_8[v >> j];
}

我要指出的最后一件事是,我很震惊我的德布林方法没有进展更好。这是我以前广泛使用的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const ulong N_bsf64 = 0x07EDD5E59A4E28C2,
            N_bsr64 = 0x03F79D71B4CB0A89;

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

public static int IndexOfLSB(ulong v) =>
    v != 0 ? bsf64[((v & (ulong)-(long)v) * N_bsf64) >> 58] : -1;

public static int IndexOfMSB(ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 64) - 1;

    v |= v >> 1; v |= v >> 2;  v |= v >> 4;   // does anybody know a better
    v |= v >> 8; v |= v >> 16; v |= v >> 32;  // way than these 12 ops?
    return bsr64[(v * N_bsr64) >> 58];
}

在这个问题上,有很多关于德布鲁伊恩方法如何优越和伟大的讨论,我倾向于同意。我的猜测是,虽然debuijn和直接查找表方法(我发现是最快的)都必须进行表查找,并且都具有非常小的分支,但是只有debuijn具有64位乘法操作。我只在这里测试了IndexOfMSB函数,而不是debuijn IndexOfLSB,但我希望后者的机会更大,因为它的操作更少(见上文),我可能会继续将其用于lsb。