Position of least significant bit that is set
我正在寻找一种有效的方法来确定在整数中设置的最低有效位的位置,例如,对于0x0ff0,它将是4。
一个简单的实现是:
1 2 3 4 5 6 7 8 9 10 11 12 | unsigned GetLowestBitPos(unsigned value) { assert(value != 0); // handled separately unsigned pos = 0; while (!(value & 1)) { value >>= 1; ++pos; } return pos; } |
有什么办法可以挤出一些周期吗?
(注:这个问题是为了那些喜欢这些东西的人,而不是为了让人们告诉我Xyzoptimization是邪恶的。)
[编辑]谢谢大家的意见!我也学到了一些其他的东西。酷!
Bit Twidling Hacks提供了一个非常好的收集,ER,Bit Twidling Hacks,附带性能/优化讨论。我最喜欢的解决方法是(从那个网站)是?乘法和查找?:
1 2 3 4 5 6 7 8 | unsigned int v; // find the number of trailing zeros in 32-bit v int r; // result goes here static const int MultiplyDeBruijnBitPosition[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 }; r = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x077CB531U)) >> 27]; |
有用的参考资料:
- "使用de bruijn序列在计算机单词中索引1"-解释为什么上述代码有效。
- "board representation>bitboards>bitscan"-对这个问题的详细分析,特别关注国际象棋编程
为什么不使用内置的FFS?(我从Linux中抓取了一个手册页,但它的可用性比这个更广。)
ffs(3) - Linux man page
Name
ffs - find first bit set in a word
Synopsis
1
2
3
4
5
6 #include <strings.h>
int ffs(int i);
#define _GNU_SOURCE
#include <string.h>
int ffsl(long int i);
int ffsll(long long int i);Description
The ffs() function returns the position of the first (least significant) bit set in the word i. The least significant bit is position 1 and the most significant position e.g. 32 or 64. The functions ffsll() and ffsl() do the same but take arguments of possibly different size.
Return Value
These functions return the position of the first bit set, or 0 if no bits are set in i.
Conforming to
4.3BSD, POSIX.1-2001.
Notes
BSD systems have a prototype in
.
有一个x86汇编指令(
更优化?!
边注:这一级别的优化本质上依赖于体系结构。今天的处理器过于复杂(在分支预测、缓存未命中、流水线等方面),以至于很难预测在哪个体系结构上更快地执行哪些代码。将操作从32减少到9或类似的情况甚至可能会降低某些体系结构上的性能。在一个体系结构上优化代码可能会导致另一个体系结构中的代码更差。我认为您要么为一个特定的CPU优化它,要么让它保持原样,让编译器选择它认为更好的东西。
大多数现代体系结构都会有一些指令来查找最低位或最高位的位置,或者计算前导零的数量等。
如果你有这个类的任何一条指令,你可以便宜地模仿其他指令。
花点时间在纸上研究一下,意识到
如果没有相关的硬件支持,那么这里给出的计数前导零的乘法和查找实现或者位旋转黑客页面上的一个前导零的乘法和查找实现都可以通过上述身份转换成最低的设置位,并且具有无分支的优点。
最快的(非内部/非汇编程序)解决方案是找到最低的字节,然后在256个条目查找表中使用该字节。这给了您四个条件指令的最坏情况性能和1的最佳情况。这不仅是指令数量最少,而且是分支数量最少,这在现代硬件中非常重要。
您的表(256个8位条目)应该包含0-255范围内每个数字的LSB索引。检查值的每个字节并找到最低的非零字节,然后使用该值查找实际索引。
这确实需要256字节的内存,但是如果这个函数的速度非常重要,那么256字节就值得了,
例如。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | byte lowestBitTable[256] = { .... // left as an exercise for the reader to generate }; unsigned GetLowestBitPos(unsigned value) { // note that order to check indices will depend whether you are on a big // or little endian machine. This is for little-endian byte* bytes = (byte*)value; if (bytes[0]) return lowestBitTable[bytes[0]]; else if (bytes[1]) return lowestBitTable[bytes[1]] + 8; else if (bytes[2]) return lowestBitTable[bytes[2]] + 16; else return lowestBitTable[bytes[3]] + 24; } |
型
WEEE,大量的解决方案,而不是一个现成的基准。你们这些人应该为自己感到羞耻。
我的机器是Inteli530(2.9GHz),运行Windows7 64位。我用32位版本的mingw编译。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | $ gcc --version gcc.exe (GCC) 4.7.2 $ gcc bench.c -o bench.exe -std=c99 -Wall -O2 $ bench Naive loop. Time = 2.91 (Original questioner) De Bruijn multiply. Time = 1.16 (Tykhyy) Lookup table. Time = 0.36 (Andrew Grant) FFS instruction. Time = 0.90 (ephemient) Branch free mask. Time = 3.48 (Dan / Jim Balter) Double hack. Time = 3.41 (DocMax) $ gcc bench.c -o bench.exe -std=c99 -Wall -O2 -march=native $ bench Naive loop. Time = 2.92 De Bruijn multiply. Time = 0.47 Lookup table. Time = 0.35 FFS instruction. Time = 0.68 Branch free mask. Time = 3.49 Double hack. Time = 0.92 |
我的代码:
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 | #include <stdio.h> #include <stdlib.h> #include <time.h> #define ARRAY_SIZE 65536 #define NUM_ITERS 5000 // Number of times to process array int find_first_bits_naive_loop(unsigned nums[ARRAY_SIZE]) { int total = 0; // Prevent compiler from optimizing out the code for (int j = 0; j < NUM_ITERS; j++) { for (int i = 0; i < ARRAY_SIZE; i++) { unsigned value = nums[i]; if (value == 0) continue; unsigned pos = 0; while (!(value & 1)) { value >>= 1; ++pos; } total += pos + 1; } } return total; } int find_first_bits_de_bruijn(unsigned nums[ARRAY_SIZE]) { static const int MultiplyDeBruijnBitPosition[32] = { 1, 2, 29, 3, 30, 15, 25, 4, 31, 23, 21, 16, 26, 18, 5, 9, 32, 28, 14, 24, 22, 20, 17, 8, 27, 13, 19, 7, 12, 6, 11, 10 }; int total = 0; // Prevent compiler from optimizing out the code for (int j = 0; j < NUM_ITERS; j++) { for (int i = 0; i < ARRAY_SIZE; i++) { unsigned int c = nums[i]; total += MultiplyDeBruijnBitPosition[((unsigned)((c & -c) * 0x077CB531U)) >> 27]; } } return total; } unsigned char lowestBitTable[256]; int get_lowest_set_bit(unsigned num) { unsigned mask = 1; for (int cnt = 1; cnt <= 32; cnt++, mask <<= 1) { if (num & mask) { return cnt; } } return 0; } int find_first_bits_lookup_table(unsigned nums[ARRAY_SIZE]) { int total = 0; // Prevent compiler from optimizing out the code for (int j = 0; j < NUM_ITERS; j++) { for (int i = 0; i < ARRAY_SIZE; i++) { unsigned int value = nums[i]; // note that order to check indices will depend whether you are on a big // or little endian machine. This is for little-endian unsigned char *bytes = (unsigned char *)&value; if (bytes[0]) total += lowestBitTable[bytes[0]]; else if (bytes[1]) total += lowestBitTable[bytes[1]] + 8; else if (bytes[2]) total += lowestBitTable[bytes[2]] + 16; else total += lowestBitTable[bytes[3]] + 24; } } return total; } int find_first_bits_ffs_instruction(unsigned nums[ARRAY_SIZE]) { int total = 0; // Prevent compiler from optimizing out the code for (int j = 0; j < NUM_ITERS; j++) { for (int i = 0; i < ARRAY_SIZE; i++) { total += __builtin_ffs(nums[i]); } } return total; } int find_first_bits_branch_free_mask(unsigned nums[ARRAY_SIZE]) { int total = 0; // Prevent compiler from optimizing out the code for (int j = 0; j < NUM_ITERS; j++) { for (int i = 0; i < ARRAY_SIZE; i++) { unsigned value = nums[i]; int i16 = !(value & 0xffff) << 4; value >>= i16; int i8 = !(value & 0xff) << 3; value >>= i8; int i4 = !(value & 0xf) << 2; value >>= i4; int i2 = !(value & 0x3) << 1; value >>= i2; int i1 = !(value & 0x1); int i0 = (value >> i1) & 1? 0 : -32; total += i16 + i8 + i4 + i2 + i1 + i0 + 1; } } return total; } int find_first_bits_double_hack(unsigned nums[ARRAY_SIZE]) { int total = 0; // Prevent compiler from optimizing out the code for (int j = 0; j < NUM_ITERS; j++) { for (int i = 0; i < ARRAY_SIZE; i++) { unsigned value = nums[i]; double d = value ^ (value - !!value); total += (((int*)&d)[1]>>20)-1022; } } return total; } int main() { unsigned nums[ARRAY_SIZE]; for (int i = 0; i < ARRAY_SIZE; i++) { nums[i] = rand() + (rand() << 15); } for (int i = 0; i < 256; i++) { lowestBitTable[i] = get_lowest_set_bit(i); } clock_t start_time, end_time; int result; start_time = clock(); result = find_first_bits_naive_loop(nums); end_time = clock(); printf("Naive loop. Time = %.2f, result = %d ", (end_time - start_time) / (double)(CLOCKS_PER_SEC), result); start_time = clock(); result = find_first_bits_de_bruijn(nums); end_time = clock(); printf("De Bruijn multiply. Time = %.2f, result = %d ", (end_time - start_time) / (double)(CLOCKS_PER_SEC), result); start_time = clock(); result = find_first_bits_lookup_table(nums); end_time = clock(); printf("Lookup table. Time = %.2f, result = %d ", (end_time - start_time) / (double)(CLOCKS_PER_SEC), result); start_time = clock(); result = find_first_bits_ffs_instruction(nums); end_time = clock(); printf("FFS instruction. Time = %.2f, result = %d ", (end_time - start_time) / (double)(CLOCKS_PER_SEC), result); start_time = clock(); result = find_first_bits_branch_free_mask(nums); end_time = clock(); printf("Branch free mask. Time = %.2f, result = %d ", (end_time - start_time) / (double)(CLOCKS_PER_SEC), result); start_time = clock(); result = find_first_bits_double_hack(nums); end_time = clock(); printf("Double hack. Time = %.2f, result = %d ", (end_time - start_time) / (double)(CLOCKS_PER_SEC), result); } |
。
型
OMG的情况刚刚恶化。
这些例子中缺少的大部分是对所有硬件如何工作的一点了解。
每当你有一个分支时,CPU必须猜测哪个分支将被占用。指令管道将加载引导猜测路径的指令。如果CPU猜测错误,那么指令管道将被刷新,并且必须加载另一个分支。
考虑顶部的简单while循环。猜测将保持在循环中。当它离开循环时,至少会出错一次。这将冲洗指令管。这种行为比猜测它将离开循环要好一点,在这种情况下,它将在每次迭代中刷新指令管道。
丢失的CPU周期量在不同类型的处理器之间变化很大。但是,您可以预期会损失20到150个CPU周期。
下一个更糟糕的组是,您认为通过将值拆分为较小的片段并添加多个分支,可以节省一些迭代。这些分支中的每一个都增加了一个刷新指令管道的额外机会,并花费了另外20到150个时钟周期。
让我们考虑一下在表中查找值时会发生什么。很可能该值当前不在缓存中,至少不是第一次调用函数。这意味着当从缓存加载值时,CPU会停止运行。这一点在不同的机器之间也有所不同。新的英特尔芯片实际上利用这个机会交换线程,而当前线程正在等待缓存加载完成。这很容易比指令管道刷新更昂贵,但是如果多次执行此操作,则可能只发生一次。
显然,最快的常数时间解是涉及确定性数学的。纯净优雅的解决方案。
如果这件事已经被解决了,我很抱歉。
我使用的每个编译器,除了xcode afaik,都有用于前位扫描和后位扫描的编译器内部函数。这些指令将在大多数硬件上编译为单个汇编指令,不存在缓存丢失、分支丢失预测和其他程序员生成的绊脚石。
对于Microsoft编译器,请使用"bitscanforward"和"bitscanreverse"。对于GCC,使用_uu builtin ffs、_u builtin_clz、_u builtin_ctz。
此外,如果您对正在讨论的主题没有足够的了解,请不要发表回答,也不要潜在地误导新来者。
抱歉,我完全忘了提供解决方案。这是我在iPad上使用的代码,它没有用于此任务的汇编级指令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | unsigned BitScanLow_BranchFree(unsigned value) { bool bwl = (value & 0x0000ffff) == 0; unsigned I1 = (bwl * 15); value = (value >> I1) & 0x0000ffff; bool bbl = (value & 0x00ff00ff) == 0; unsigned I2 = (bbl * 7); value = (value >> I2) & 0x00ff00ff; bool bnl = (value & 0x0f0f0f0f) == 0; unsigned I3 = (bnl * 3); value = (value >> I3) & 0x0f0f0f0f; bool bsl = (value & 0x33333333) == 0; unsigned I4 = (bsl * 1); value = (value >> I4) & 0x33333333; unsigned result = value + I1 + I2 + I3 + I4 - 1; return result; } |
这里要理解的是,不是比较昂贵,而是比较之后发生的分支。在这种情况下,必须将值0或1与..==0,结果用于组合分支任一侧发生的数学运算。
编辑:
上面的代码完全被破坏了。此代码可以工作,并且仍然是无分支的(如果已优化):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | int BitScanLow_BranchFree(ui value) { int i16 = !(value & 0xffff) << 4; value >>= i16; int i8 = !(value & 0xff) << 3; value >>= i8; int i4 = !(value & 0xf) << 2; value >>= i4; int i2 = !(value & 0x3) << 1; value >>= i2; int i1 = !(value & 0x1); int i0 = (value >> i1) & 1? 0 : -32; return i16 + i8 + i4 + i2 + i1 + i0; } |
号
如果给定0,则返回-1。如果您不关心0,或者希望0得到31,请删除i0计算,节省大量时间。
受这篇涉及搜索一个集合位的类似文章启发,我提供了以下内容:
1 2 3 4 5 | unsigned GetLowestBitPos(unsigned value) { double d = value ^ (value - !!value); return (((int*)&d)[1]>>20)-1023; } |
赞成的意见:
- 无回路
- 不分叉
- 在恒定时间内运行
- 通过返回其他越界结果来处理值=0
- 只有两行代码
欺骗:
- 假定编码后具有小的endianness(可以通过更改常量来修复)
- 假设double是一个实的*8 ieee float(ieee 754)
更新:正如评论中指出的,联合是一种更清洁的实现(至少对于C而言),它看起来像:
1 2 3 4 5 6 7 8 | unsigned GetLowestBitPos(unsigned value) { union { int i[2]; double d; } temp = { .d = value ^ (value - !!value) }; return (temp.i[1] >> 20) - 1023; } |
这假设32位的ints具有很小的endian存储空间(想想x86处理器)。
为什么不使用二进制搜索?这将始终在5次操作后完成(假定int大小为4字节):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | if (0x0000FFFF & value) { if (0x000000FF & value) { if (0x0000000F & value) { if (0x00000003 & value) { if (0x00000001 & value) { return 1; } else { return 2; } } else { if (0x0000004 & value) { return 3; } else { return 4; } } } else { ... } else { ... } else { ... |
最坏的情况是少于32次操作:
原理:检查2位或更多位与检查1位一样有效。
例如,没有什么能阻止你先检查哪个分组,然后检查该分组中从最小到最大的每一位。
所以…如果一次检查2位,则在最坏情况下(nbits/2)+1检查总数。如果一次检查3位,则在最坏情况下(nbits/3)+总共检查2次。…
最好是4人一组登记。在最坏的情况下需要11次操作而不是32次。
最好的情况是从算法的1个检查到2个检查,如果您使用这种分组思想。但在最好的情况下,额外的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 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 | int getLowestBitPos(unsigned int value) { //Group 1: Bits 0-3 if(value&0xf) { if(value&0x1) return 0; else if(value&0x2) return 1; else if(value&0x4) return 2; else return 3; } //Group 2: Bits 4-7 if(value&0xf0) { if(value&0x10) return 4; else if(value&0x20) return 5; else if(value&0x40) return 6; else return 7; } //Group 3: Bits 8-11 if(value&0xf00) { if(value&0x100) return 8; else if(value&0x200) return 9; else if(value&0x400) return 10; else return 11; } //Group 4: Bits 12-15 if(value&0xf000) { if(value&0x1000) return 12; else if(value&0x2000) return 13; else if(value&0x4000) return 14; else return 15; } //Group 5: Bits 16-19 if(value&0xf0000) { if(value&0x10000) return 16; else if(value&0x20000) return 17; else if(value&0x40000) return 18; else return 19; } //Group 6: Bits 20-23 if(value&0xf00000) { if(value&0x100000) return 20; else if(value&0x200000) return 21; else if(value&0x400000) return 22; else return 23; } //Group 7: Bits 24-27 if(value&0xf000000) { if(value&0x1000000) return 24; else if(value&0x2000000) return 25; else if(value&0x4000000) return 26; else return 27; } //Group 8: Bits 28-31 if(value&0xf0000000) { if(value&0x10000000) return 28; else if(value&0x20000000) return 29; else if(value&0x40000000) return 30; else return 31; } return -1; } |
型
根据国际象棋程序的位扫描页面和我自己的测量,减法和异或比求反和蒙版快。
(注意,如果您要计算
下面是一个64位的减法和异或:
1 2 3 4 5 6 7 8 9 10 | unsigned long v; // find the number of trailing zeros in 64-bit v int r; // result goes here static const int MultiplyDeBruijnBitPosition[64] = { 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 }; r = MultiplyDeBruijnBitPosition[((uint32_t)((v ^ (v-1)) * 0x03F79D71B4CB0A89U)) >> 58]; |
下面是一个64位版本的negate和mask方法,供参考:
1 2 3 4 5 6 7 8 9 10 | unsigned long v; // find the number of trailing zeros in 64-bit v int r; // result goes here static const int MultiplyDeBruijnBitPosition[64] = { 0, 1, 48, 2, 57, 49, 28, 3, 61, 58, 50, 42, 38, 29, 17, 4, 62, 55, 59, 36, 53, 51, 43, 22, 45, 39, 33, 30, 24, 18, 12, 5, 63, 47, 56, 27, 60, 41, 37, 16, 54, 35, 52, 21, 44, 32, 23, 11, 46, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6 }; r = MultiplyDeBruijnBitPosition[((uint32_t)((v & -v) * 0x03F79D71B4CB0A89U)) >> 58]; |
。
另一种方法(模除和查找)值得在这里从@anton tykhyy提供的相同链接中特别提到。该方法在性能上与DEBRUIJN乘法查找方法非常相似,但有细微但重要的区别。
模除与查找
1 2 3 4 5 6 7 8 9 | unsigned int v; // find the number of trailing zeros in v int r; // put the result in r static const int Mod37BitPosition[] = // map a bit value mod 37 to its position { 32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30, 28, 11, 0, 13, 4, 7, 17, 0, 25, 22, 31, 15, 29, 10, 12, 6, 0, 21, 14, 9, 5, 20, 8, 19, 18 }; r = Mod37BitPosition[(-v & v) % 37]; |
对于v=0x0000000和v=ffffffff,模除和查找方法返回不同的值,而对于这两个输入,debuijn乘法和查找方法返回零。
测试:
1 2 3 4 5 6 | unsigned int n1=0x00000000, n2=0xFFFFFFFF; MultiplyDeBruijnBitPosition[((unsigned int )((n1 & -n1) * 0x077CB531U)) >> 27]); /* returns 0 */ MultiplyDeBruijnBitPosition[((unsigned int )((n2 & -n2) * 0x077CB531U)) >> 27]); /* returns 0 */ Mod37BitPosition[(((-(n1) & (n1))) % 37)]); /* returns 32 */ Mod37BitPosition[(((-(n2) & (n2))) % 37)]); /* returns 0 */ |
另一个解决方案,可能不是最快的,但似乎相当不错。至少它没有分支。;)
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 | uint32 x = ...; // 0x00000001 0x0405a0c0 0x00602000 x |= x << 1; // 0x00000003 0x0c0fe1c0 0x00e06000 x |= x << 2; // 0x0000000f 0x3c3fe7c0 0x03e1e000 x |= x << 4; // 0x000000ff 0xffffffc0 0x3fffe000 x |= x << 8; // 0x0000ffff 0xffffffc0 0xffffe000 x |= x << 16; // 0xffffffff 0xffffffc0 0xffffe000 // now x is filled with '1' from the least significant '1' to bit 31 x = ~x; // 0x00000000 0x0000003f 0x00001fff // now we have 1's below the original least significant 1 // let's count them x = x & 0x55555555 + (x >> 1) & 0x55555555; // 0x00000000 0x0000002a 0x00001aaa x = x & 0x33333333 + (x >> 2) & 0x33333333; // 0x00000000 0x00000024 0x00001444 x = x & 0x0f0f0f0f + (x >> 4) & 0x0f0f0f0f; // 0x00000000 0x00000006 0x00000508 x = x & 0x00ff00ff + (x >> 8) & 0x00ff00ff; // 0x00000000 0x00000006 0x0000000d x = x & 0x0000ffff + (x >> 16) & 0x0000ffff; // 0x00000000 0x00000006 0x0000000d // least sign.bit pos. was: 0 6 13 |
使用"编程艺术,第4部分"中的"魔法面具"发现了这个巧妙的技巧,它在O(log(n))时间中实现了n位数字。[带日志(n)额外空间]。检查设置位的典型解决方案是O(N)或需要O(N)额外空间作为查找表,因此这是一个很好的折衷方案。
魔术面具:
1 2 3 4 5 | m0 = (...............01010101) m1 = (...............00110011) m2 = (...............00001111) m3 = (.......0000000011111111) .... |
关键理念:x=1*[(x&;m0)=0]+2*[(x&;m1)=0]+4*[(x&;m2)=0]+…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | int lastSetBitPos(const uint64_t x) { if (x == 0) return -1; //For 64 bit number, log2(64)-1, ie; 5 masks needed int steps = log2(sizeof(x) * 8); assert(steps == 6); //magic masks uint64_t m[] = { 0x5555555555555555, // .... 010101 0x3333333333333333, // .....110011 0x0f0f0f0f0f0f0f0f, // ...00001111 0x00ff00ff00ff00ff, //0000000011111111 0x0000ffff0000ffff, 0x00000000ffffffff }; //Firstly extract only the last set bit uint64_t y = x & -x; int trailZeros = 0, i = 0 , factor = 0; while (i < steps) { factor = ((y & m[i]) == 0 ) ? 1 : 0; trailZeros += factor * pow(2,i); ++i; } return (trailZeros+1); } |
如果C++ 11可供你使用,编译器有时可以为你完成任务:
1 2 3 4 | constexpr std::uint64_t lssb(const std::uint64_t value) { return !value ? 0 : (value % 2 ? 1 : lssb(value >> 1) + 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 29 30 31 32 33 34 | unsigned GetLowestBitPos(unsigned value) { if (value & 1) return 1; if (value & 2) return 2; if (value & 4) return 3; if (value & 8) return 4; if (value & 16) return 5; if (value & 32) return 6; if (value & 64) return 7; if (value & 128) return 8; if (value & 256) return 9; if (value & 512) return 10; if (value & 1024) return 11; if (value & 2048) return 12; if (value & 4096) return 13; if (value & 8192) return 14; if (value & 16384) return 15; if (value & 32768) return 16; if (value & 65536) return 17; if (value & 131072) return 18; if (value & 262144) return 19; if (value & 524288) return 20; if (value & 1048576) return 21; if (value & 2097152) return 22; if (value & 4194304) return 23; if (value & 8388608) return 24; if (value & 16777216) return 25; if (value & 33554432) return 26; if (value & 67108864) return 27; if (value & 134217728) return 28; if (value & 268435456) return 29; if (value & 536870912) return 30; return 31; } |
所有数字的50%将返回到代码的第一行。
所有数字的75%将返回前2行代码。
所有数字的87%将返回前3行代码。
所有数字的94%将返回前4行代码。
所有数字的97%将返回前5行代码。
等。
我认为那些抱怨这段代码的最坏情况是多么低效的人不理解这种情况会发生得有多罕见。
您可以检查是否设置了任何低阶位。如果是这样,则查看剩余位的低阶。例如。,:
32位int-检查前16位是否已设置。如果是,请检查前8个是否已设置。如果是这样的话…
如果没有,请检查是否设置了上16个。
基本上是二进制搜索。
有关如何使用单个x86指令执行此操作,请参阅此处的答案,除了要查找最低有效的设置位,您需要使用
这里有一个简单的选择,尽管查找日志有点昂贵。
1 2 3 | if(n == 0) return 0; return log2(n & -n)+1; //Assuming the bit index starts from 1 |
这是关于@anton tykhyy的答案
下面是我的C++ 11 CONTXPR实现,通过将64位结果截断为32位,消除了CVASE并删除了VC++ 17上的警告:
1 2 3 4 5 6 7 8 9 10 11 | constexpr uint32_t DeBruijnSequence[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 }; constexpr uint32_t ffs ( uint32_t value ) { return DeBruijnSequence[ (( ( value & ( -static_cast<int32_t>(value) ) ) * 0x077CB531ULL ) & 0xFFFFFFFF) >> 27]; } |
要解决0x1和0x0都返回0的问题,可以执行以下操作:
1 2 3 4 5 6 | constexpr uint32_t ffs ( uint32_t value ) { return (!value) ? 32 : DeBruijnSequence[ (( ( value & ( -static_cast<int32_t>(value) ) ) * 0x077CB531ULL ) & 0xFFFFFFFF) >> 27]; } |
但是,如果编译器不能或不会对调用进行预处理,它将为计算添加几个周期。
最后,如果感兴趣,这里是一个静态断言列表,用于检查代码是否执行了预期的操作:
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 | static_assert (ffs(0x1) == 0,"Find First Bit Set Failure."); static_assert (ffs(0x2) == 1,"Find First Bit Set Failure."); static_assert (ffs(0x4) == 2,"Find First Bit Set Failure."); static_assert (ffs(0x8) == 3,"Find First Bit Set Failure."); static_assert (ffs(0x10) == 4,"Find First Bit Set Failure."); static_assert (ffs(0x20) == 5,"Find First Bit Set Failure."); static_assert (ffs(0x40) == 6,"Find First Bit Set Failure."); static_assert (ffs(0x80) == 7,"Find First Bit Set Failure."); static_assert (ffs(0x100) == 8,"Find First Bit Set Failure."); static_assert (ffs(0x200) == 9,"Find First Bit Set Failure."); static_assert (ffs(0x400) == 10,"Find First Bit Set Failure."); static_assert (ffs(0x800) == 11,"Find First Bit Set Failure."); static_assert (ffs(0x1000) == 12,"Find First Bit Set Failure."); static_assert (ffs(0x2000) == 13,"Find First Bit Set Failure."); static_assert (ffs(0x4000) == 14,"Find First Bit Set Failure."); static_assert (ffs(0x8000) == 15,"Find First Bit Set Failure."); static_assert (ffs(0x10000) == 16,"Find First Bit Set Failure."); static_assert (ffs(0x20000) == 17,"Find First Bit Set Failure."); static_assert (ffs(0x40000) == 18,"Find First Bit Set Failure."); static_assert (ffs(0x80000) == 19,"Find First Bit Set Failure."); static_assert (ffs(0x100000) == 20,"Find First Bit Set Failure."); static_assert (ffs(0x200000) == 21,"Find First Bit Set Failure."); static_assert (ffs(0x400000) == 22,"Find First Bit Set Failure."); static_assert (ffs(0x800000) == 23,"Find First Bit Set Failure."); static_assert (ffs(0x1000000) == 24,"Find First Bit Set Failure."); static_assert (ffs(0x2000000) == 25,"Find First Bit Set Failure."); static_assert (ffs(0x4000000) == 26,"Find First Bit Set Failure."); static_assert (ffs(0x8000000) == 27,"Find First Bit Set Failure."); static_assert (ffs(0x10000000) == 28,"Find First Bit Set Failure."); static_assert (ffs(0x20000000) == 29,"Find First Bit Set Failure."); static_assert (ffs(0x40000000) == 30,"Find First Bit Set Failure."); static_assert (ffs(0x80000000) == 31,"Find First Bit Set Failure."); |
最近我看到新加坡总理在facebook上发布了一个他写的节目,有一句话要说。
逻辑只是"value&;-value",假设您有0x0ff0,那么,0ff0&;(f00f+1),等于0x0010,表示最低的1在第4位。:)
如果您有资源,可以牺牲内存以提高速度:
1 2 3 4 5 6 7 | static const unsigned bitPositions[MAX_INT] = { 0, 0, 1, 0, 2, /* ... */ }; unsigned GetLowestBitPos(unsigned value) { assert(value != 0); // handled separately return bitPositions[value]; } |
注:此表将消耗至少4 GB(如果我们将返回类型保留为
如果您的功能需要保持可移植性,并以任何代价尽可能快地运行,那么这将是一种可行的方法。在大多数实际应用中,4GB表是不现实的。