What is the fastest/most efficient way to find the highest set bit (msb) in an integer in C?
如果我有一个整数n,我想知道最有效位的位置(也就是说,如果最无效位在右边,我想知道最左边位的位置是1),那么找出最快/最有效的方法是什么?
我知道posix在strings.h中支持
有没有什么明显的方法可以做到这一点?
在不能使用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'. |
我希望它们能被转换成对您当前平台相当有效的东西,无论是那些花哨的比特旋转算法,还是一条指令。
如果您的输入可以为零,一个有用的技巧是
为了避免需要这样做,您的另一个选择是特定于平台的内部机制,比如ARMGCC的
不幸的是,在非x86平台上,没有办法可以移植地利用各种CLZ指令,这些指令确实将input=0的结果定义为32或64(根据操作数宽度)。x86的
(未定义的结果不是C未定义的行为,只是一个未定义的值。它实际上是在指令运行时目标寄存器中的任何内容。AMD记录了这一点,英特尔没有,但英特尔的CPU确实实现了这一行为。但这并不是您要分配给的C变量中以前的内容,当GCC将C转换为ASM时,这通常不是事情的工作方式。另请参见为什么打破LZCNT的"输出依赖关系"很重要?)
型
假设您使用的是X86和游戏中的一些内嵌汇编程序,Intel将提供一条
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上,有一个类似的
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及更高版本中,使用
对于前导零计数指令,这通常比平台特定的内部函数慢,但便携式ISO C没有这样的功能。一些CPU还缺少前导零计数指令,但其中一些CPU可以有效地将整数转换为
这种算法可能对SIMD实现有用,因为拥有SIMD
这里是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 |
?
型
尽管我可能只会在绝对需要最佳性能(例如,对于编写涉及bitboard的某种棋盘游戏ai)的情况下使用此方法,但最有效的解决方案是使用内联asm。有关带解释的代码,请参阅此博客文章的"优化"部分。
[...], the
bsrl assembly instruction computes the position of the most significant bit. Thus, we could use thisasm 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绝对是最快的,甚至比我测试过的任何处理器上的
如果内在函数不是一个选项,这里是处理一般输入的最佳软件解决方案。
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给了我们
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) |
需要对
-∞ is returned and FE_DIVBYZERO is raised
我写了一个例子,在这里随意地将
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) |
Loaded with the bit position of the first set bit (1) found
在实践中,我发现如果
0 if no set bits were found
因此,与上面的首选
使用逐次逼近的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(这是"长"版本)。正常情况下可能会快一点。
既然这是"又一个"方法,那么把它放进去,似乎与其他已经给出的方法不同。
如果
从32位减少到4位,然后使用表。也许不雅,但实用。
这是我在不想使用
为了使它更紧凑,可以使用循环来减少,每次增加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。
有趣的部分是初始化表。由于它是我们在进程生命周期中需要的一个相对较小的块,所以我使用
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位)的日志。
注意,
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加上值
这里是我正式答案的结尾;下面是一些临时注释和到源代码的链接,这些源代码是与我运行以验证上述代码的性能和正确性的测试相关联的备选测试候选的源代码。
上面提供的版本,编码为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
值得注意的是,
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)的
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位乘法操作。我只在这里测试了