如果我有一些整数n,并且我想知道最高有效位的位置(也就是说,如果最低有效位在右边,那么我想知道最远的左位为1),找出最快/最有效的方法是什么?
我知道POSIX ffs()
在strings.h中支持一种方法来查找第一个设置位,但是似乎没有相应的fls()
方法。
是否有一些我确实不知道的真正明显的方法?
如果无法使用POSIX函数进行可移植性该怎么办?
编辑:在32位和64位体系结构上均可使用的解决方案怎么样(许多代码清单似乎只在32位int上运行)。
如果我有一些整数n,并且我想知道最高有效位的位置(也就是说,如果最低有效位在右边,那么我想知道最远的左位为1),找出最快/最有效的方法是什么?
我知道POSIX ffs()
在strings.h中支持一种方法来查找第一个设置位,但是似乎没有相应的fls()
方法。
是否有一些我确实不知道的真正明显的方法?
如果无法使用POSIX函数进行可移植性该怎么办?
编辑:在32位和64位体系结构上均可使用的解决方案怎么样(许多代码清单似乎只在32位int上运行)。
Answers:
-内置函数:int __builtin_clz(unsigned int x) 返回X中从0开始的前导0位的数量 有效位位置。如果X为0,则结果不确定。 -内置函数:int __builtin_clzl(unsigned long) 类似于__builtin_clz,除了参数类型为unsigned 长'。 -内置函数:int __builtin_clzll(unsigned long long) 类似于__builtin_clz,除了参数类型为unsigned 很久很久。
我希望它们可以被转换为适合您当前平台的某种有效方式,无论是那些奇特的位旋转算法之一还是一条指令。
一个有用的技巧,如果你的输入可以是零是__builtin_clz(x | 1)
:无条件设置低位,而无需修改任何其他使输出31
的x=0
,在不改变输出的任何其他输入。
为了避免这样做,您的另一个选择是特定于平台的内部函数,例如ARM GCC __clz
(不需要标头)或_lzcnt_u32
支持lzcnt
指令的CPU上的x86 。(请注意,lzcnt
它bsr
在较旧的CPU上进行解码而不是出错,这会为非零输入提供31-lzcnt。)
不幸的是,没有办法在非x86平台上可移植地利用各种CLZ指令,这些指令确实将input = 0的结果定义为32或64(根据操作数宽度)。x86 lzcnt
也会这样做,同时bsr
会生成一个位索引,除非您使用,否则编译器必须翻转该位索引31-__builtin_clz(x)
。
(“未定义结果”不是C未定义行为,只是一个未定义的值。实际上是指令运行时目标寄存器中的值。AMD记录了这一点,英特尔没有,但是英特尔的CPU确实实现了该行为。 。但是,这并不是您之前要分配给C变量的内容,而是gcc将C转换为asm时通常无法正常工作。另请参见为什么破坏LZCNT的“输出依赖关系”很重要?)
__builtin_ctz
over来说是一个很大的优势ffs
,它将编译为BSF和CMOV以处理输入为零的情况。在没有足够短的实现的体系结构上(例如,没有clz
指令的旧ARM ),gcc发出对libgcc helper函数的调用。
假设您正在使用x86和游戏进行一些内联汇编程序,英特尔将提供一条BSR
说明(“反向位扫描”)。在某些 x86 上速度很快(在其他x86上微编码)。从手册中:
在源操作数中搜索最高有效位(1位)。如果找到最高有效的1位,则其位索引存储在目标操作数中。源操作数可以是寄存器或内存位置;目标操作数是一个寄存器。位索引是源操作数的位0的无符号偏移量。如果内容源操作数为0,则目标操作数的内容未定义。
(如果您使用的是PowerPC,则有类似的cntlz
(“前导零计数”)指令。)
gcc的示例代码:
#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;
}
由于2 ^ N是仅设置第N个位(1 << N)的整数,因此找到最高设置位的位置(N)是该整数的对数整数2。
http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
unsigned int v;
unsigned r = 0;
while (v >>= 1) {
r++;
}
这种“显而易见的”算法可能并非对所有人都透明,但是当您意识到代码反复向右移一位直到最左边的位被移走(请注意,C将任何非零值视为true)并返回数字时,的变化,这是很有意义的。这也意味着即使设置了多个位,它也可以工作-结果始终是最高有效位。
如果您向下滚动该页面,则会有更快,更复杂的版本。但是,如果您知道要处理带有很多前导零的数字,那么天真的方法可能会提供可接受的速度,因为C语言中的位移位相当快,而且简单的算法不需要索引数组。
注意:使用64位值时,请谨慎使用额外的智能算法;它们中的许多仅对于32位值正确工作。
>>>
。加上比较器!= 0
,以及一些未指定的括号。
这应该快如闪电:
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已经有一个自动位检测器,用于整数到浮点转换!所以用那个。
double ff=(double)(v|1);
return ((*(1+(uint32_t *)&ff))>>20)-1023; // assumes x86 endianness
此版本将值强制转换为双精度,然后读取指数,从而告诉您该位在何处。花式移位和减去是从IEEE值中提取适当的部分。
使用浮点数的速度稍快一些,但是浮点数只能提供前24位的位置,因为它的精度较低。
为了安全地执行此操作,而在C ++或C中没有未定义的行为,请使用memcpy
而不是指针强制转换进行类型处理。编译器知道如何有效地内联它。
// 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也缺少前导零计数指令,但是其中一些可以将整数有效地转换为double
。但是,将FP位模式类型化回整数可能会很慢(例如,在PowerPC上,它需要存储/重新加载,通常会导致加载命中-存储停顿)。
该算法可能对SIMD实现很有用,因为具有SIMD的CPU更少lzcnt
。x86仅在AVX512CD上获得了这样的指令
卡兹(Kaz Kylheku)在这里
我为此在63个以上的位数(gcc x86_64上的long long类型)上确定了两种方法的基准,并且远离符号位。
(您发现我碰巧需要此“查找最高位”)
我实现了数据驱动的二进制搜索(紧密基于以上答案之一)。我还手动实现了一个完全展开的决策树,它只是带有立即操作数的代码。没有循环,没有表。
基准树(highest_bit_unrolled)的基准测试速度提高了69%,但n = 0的情况除外(二进制搜索对其进行了显式测试)。
二进制搜索针对0情况的特殊测试仅比没有特殊测试的决策树快48%。
机器编译器:(GCC 4.5.2,-O3,x86-64、2867 Mhz Intel Core i5)。
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;
}
快速而肮脏的测试程序:
#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, n, b1, b2);
printf("time1 = %d\n", (int) (mid - start));
printf("time2 = %d\n", (int) (end - mid));
return 0;
}
仅使用-O2,差异会更大。决策树快了将近四倍。
我还以朴素的位移代码为基准:
int highest_bit_shift(long long n)
{
int i = 0;
for (; n; n >>= 1, i++)
; /* empty */
return i;
}
正如人们所期望的那样,这仅对于少量用户而言是快速的。在确定n == 1的最高位为1时,基准测试速度提高了80%以上。但是,在63位空间中随机选择的数字的一半设置了第63位!
在输入0x3FFFFFFFFFFFFFFFFF上,决策树版本比1上快很多,并且显示出比移位器快1120%(12.2倍)。
我还将针对GCC内建函数对决策树进行基准测试,并尝试混合输入而不是重复相同的输入。可能会有一些分支分支预测正在进行中,也许还有一些不切实际的缓存场景,这使得它在重复上人为地变得更快。
关于什么
int highest_bit(unsigned int a) {
int count;
std::frexp(a, &count);
return count - 1;
}
?
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指令要快。这是对数时间。
__builtin_clz
启用了-march=native
或其他功能的情况(因为在支持它的每个CPU上运行速度都很快)。即使在像Bull慢的AMD Bulldozer系列这样的CPU上,也不是那么慢:7 m-ops具有4个周期的延迟和每4c吞吐量一个。在Atom上,BSR 确实很慢:16个周期。在Silvermont上,这是10微秒,有10个周期的延迟。这可能比Silvermont上的BSR(但IDK)略低。
这是本页当前提供的一些(简单)基准测试算法...
该算法尚未在unsigned int的所有输入上进行过测试;因此,请先检查一下,然后再盲目使用;)
在我的机器上,clz(__builtin_clz)和asm效果最佳。asm似乎比clz还要快...但这可能是由于简单的基准测试所致...
//////// 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\n");
for (ui = 0U; ui < 18; ++ui)
printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITmath(ui));
printf("\n\n");
printf("clz\n");
for (ui = 0U; ui < 18U; ++ui)
printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITclz(ui));
printf("\n\n");
printf("i2f\n");
for (ui = 0U; ui < 18U; ++ui)
printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITi2f(ui));
printf("\n\n");
printf("asm\n");
for (ui = 0U; ui < 18U; ++ui) {
printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITasm(ui));
}
printf("\n\n");
printf("bitshift1\n");
for (ui = 0U; ui < 18U; ++ui) {
printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift1(ui));
}
printf("\n\n");
printf("bitshift2\n");
for (ui = 0U; ui < 18U; ++ui) {
printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift2(ui));
}
printf("\n\nPlease wait...\n\n");
/************************* Simple clock() benchmark ******************/
start = clock();
for (ui = 0; ui < LOOPS; ++ui)
n = NUM_OF_HIGHESTBITmath(ui);
end = clock();
printf("math:\t%e\n", (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\n", (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\n", (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\n", (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\n", (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\n", (double)(end-start)/CLOCKS_PER_SEC);
printf("\nThe lower, the better. Take note that a negative exponent is good! ;)\n");
return EXIT_SUCCESS;
}
尽管我可能只在绝对需要最佳性能的情况下才使用此方法(例如,编写涉及位板的棋盘游戏AI),但最有效的解决方案是使用嵌入式ASM。有关说明的代码,请参见此博客文章的“优化”部分。
[...],将
bsrl
汇编指令计算最显著位的位置。因此,我们可以使用以下asm
语句:asm ("bsrl %1, %0" : "=r" (position) : "r" (number));
我需要一个例程来执行此操作,并且在搜索网络(并找到此页面)之前,我想出了自己的基于二进制搜索的解决方案。虽然我敢肯定有人做过!它以固定的时间运行,并且比发布的“显而易见的”解决方案要快,尽管我并没有提出任何出色的主张,只是出于兴趣而发布它。
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;
}
多数民众赞成在某种形式的二进制搜索,它适用于所有类型的(无符号!)整数类型
#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;
}
完成:
#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;
}
typedef
s或除预处理程序宏以外的任何其他内容。这是一个被广泛接受的惯例。
这里有些过于复杂的答案。仅当输入已经是2的幂时,才应使用Debruin技术,否则会有更好的方法。对于2输入的幂,Debruin是绝对最快的,甚至比_BitScanReverse
我测试过的任何处理器都要快。但是,在一般情况下,_BitScanReverse
(或在编译器中调用的任何内在函数)是最快的(尽管在某些CPU上可以对其进行微编码)。
如果不是固有功能,那么这里是处理常规输入的最佳软件解决方案。
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;
}
请注意,与大多数其他答案不同,此版本不需要在末尾进行Debruin查找。它计算到位。
但是,表可能是更可取的,如果您反复调用多次,则表的加速会掩盖缓存未命中的风险。
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;
}
在这里给出的所有软件答案中,这应该产生最高的吞吐量,但是如果您仅偶尔调用它,则更喜欢无表式解决方案,例如我的第一个代码段。
正如上面的答案所指出的,有很多方法可以确定最高有效位。但是,正如还指出的那样,这些方法可能对于32位或64位寄存器都是唯一的。该stanford.edu bithacks页面提供的解决方案,无论对于32位和64位计算的工作。只需少量工作,便可以将它们组合起来,以提供一种可靠的跨体系结构方法来获得MSB。我在64位和32位计算机上编译/工作的解决方案是:
#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;
}
#ifdef BUILD_64
标志上方定义?在这种情况下,将不需要在条件内重新定义。
使用逐次逼近的C版本:
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个循环)
msb += (n>>msb) ? step : -step;
)编写,则更多的编译器可能会生成无分支的asm,从而避免了每一步的分支预测错误(stackoverflow.com/questions/11227809/…)。
我知道这个问题很老,但是我自己实现了msb()函数后,发现此处和其他网站上提出的大多数解决方案不一定是最有效的-至少出于我个人对效率的定义(另请参见下面的更新))。原因如下:
大多数解决方案(尤其是那些采用某种二进制搜索方案或从右到左进行线性扫描的朴素方法的解决方案)似乎忽略了以下事实:对于任意二进制数,没有很多以很长的序列开头的零。实际上,对于任何位宽,所有整数的一半以1开头,四分之一以01开头。看看我要去哪里?我的观点是,从最高有效位位置到最低有效位(从左到右)开始的线性扫描并不像乍看上去那样“线性”。
可以显示1,对于任何位宽,需要测试的平均位数最多为2。这将转换为O(1)相对于位数(!)的摊销时间复杂度。。
当然,最坏的情况仍然是O(n),比通过类似二进制搜索的方法得到的O(log(n))还要糟糕,但是由于最坏的情况很少,因此对于大多数应用程序而言,它们可以忽略不计(Update:不完全:可能很少,但是它们发生的可能性很高-请参阅下面的更新)。
这是我提出的“天真的”方法,至少在我的机器上,它比大多数其他方法都要好(针对32位整数的二进制搜索方案始终需要log 2(32)= 5步,而这种愚蠢的算法需要更少的log平均超过2)-抱歉,这是C ++而不是纯C:
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的应用程序所涉及的数字远小于整数可以表示的最大数字(对象大小很少利用 size_t中的所有位)。在这种情况下,我的解决方案实际上将比二进制搜索方法执行得更差-因此,即使我的解决方案循环遍历所有整数的速度更快,也应首选后者。
TL; DR:现实生活中的整数可能会偏向于这种简单算法的最坏情况,这最终会使它的性能更差-尽管事实上对真正任意整数均摊销了 O(1)。
1该参数如下所示(草稿):令n为位数(位宽)。总共可以用n位表示2个n整数。有2 N - 1个整数开始与1(第一1是固定的,其余的N - 1位可以是任何东西)。那些整数仅需要循环的一个中断即可确定MSB。此外,有2个n-2的整数(从01开始),需要2次迭代,2 n-3的整数,从001开始,需要3次迭代,依此类推。
如果将所有可能的整数的所有必需的迭代求和并除以2 n(整数总数),我们将获得确定n位整数的MSB所需的平均迭代数:
(1 * 2 n-1 + 2 * 2 n-2 + 3 * 2 n-3 + ... + n)/ 2 n
因此,对于任何数量的位,幼稚的从左到右算法实际上都具有O(1)的摊销常数时间复杂度。
c99给了我们log2
。这消除了log2
您在此页面上看到的所有特殊调味料实现的需求。您可以log2
像下面这样使用标准的实现:
const auto n = 13UL;
const auto Index = (unsigned long)log2(n);
printf("MSB is: %u\n", Index); // Prints 3 (zero offset)
还需要注意n
of 0UL
,因为:
返回-∞并引发FE_DIVBYZERO
我已经编写了一个带有任意设置Index
为ULONG_MAX
此处的支票的示例:https : //ideone.com/u26vsi
的 视觉工作室对ephemient的gcc的推论唯一的答案是:
const auto n = 13UL;
unsigned long Index;
_BitScanReverse(&Index, n);
printf("MSB is: %u\n", Index); // Prints 3 (zero offset)
有关_BitScanReverse
状态的文档为Index
:
加载找到的第一个设置位(1)的位位置
在实践中我发现,如果n
是0UL
那个Index
被设定为0UL
,只因为这将是一个n
的1UL
。但在一个情况下的文档中保证唯一n
的0UL
是,返回的是:
如果未找到设置位,则为0
因此,类似于log2
上面的优选实施方式,Index
在这种情况下,应检查返回设置为标记值。我再次ULONG_MAX
在此处编写了使用此标志值的示例:http : //rextester.com/GCU61409
_BitScanReverse
使用基于零的索引,因此,如果n
为1,则设置位的索引实际上为0。不幸的是,正如您所说的,如果n
为0,则输出也为0 :(这意味着无法使用return来区分n
是1还是0,这就是我要传达的信息。您认为有更好的说法吗?
Index
。那不是返回值。如果输入为零,则返回一个布尔值为false的布尔值(这就是为什么Index通过引用传递而不是正常返回)。 godbolt.org/g/gQKJdE。我检查了一下:尽管有MS文档的措辞,_BitScanReverse
但也没有保留Index不变n==0
:您只要在寄存器中获取碰巧要使用的任何值即可。(在您的情况下,该寄存器可能与Index
之后使用的寄存器相同,导致您看到0
)。
log2
自C99以来,我们实际上就遇到过。
考虑按位运算符。
第一次我误解了这个问题。您应该使用最左边的位(其余为零)生成一个整数。假设cmp设置为该值:
position = sizeof(int)*8
while(!(n & cmp)){
n <<=1;
position--;
}
8
应该是CHAR_BIT
。这极不可能是最快的方法,因为分支错误预测将在退出循环时发生,除非将其重复与相同的输入重复使用。同样,对于小输入(很多零),它必须循环很多。这就像您在单元测试中用作易于验证版本以与优化版本进行比较的后备方式一样。
请注意,您要执行的操作是计算整数的整数log2,
#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\n",x, (x - (n >> 1)) );
return(x - (n >> 1));
}
请注意,您可以尝试一次搜索超过1位。
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\n",step2);
for( step = 0; x>1L<<(step+step2); ) {
step+=1;
//printf("step %d\n",step+step2);
}
printf("log2(%ld) %d\n",x,step+step2);
return(step+step2);
}
这种方法使用二进制搜索
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\n",x,lbit,guess,hbit);
//when value between guess..lbit
if( (x<=(1L<<guess)) ) {
//printf("%ld < 1<<%d %ld\n",x,guess,1L<<guess);
hbit=guess;
guess=(hbit+lbit)/2;
//printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
}
//when value between hbit..guess
//else
if( (x>(1L<<guess)) ) {
//printf("%ld > 1<<%d %ld\n",x,guess,1L<<guess);
lbit=guess;
guess=(hbit+lbit)/2;
//printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
}
}
if( (x>(1L<<guess)) ) ++guess;
printf("log2(x%ld)=r%d\n",x,guess);
return(guess);
}
另一种可能更易读的二进制搜索方法,
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\n",x,v,step,v>>step);
while ( v>>step ) {
v>>=step;
res+=step;
//printf("log2(%ld) step %d res %d v>>step %ld\n",x,step,res,v);
}
step /= 2;
}
if( (x>(1L<<res)) ) ++res;
printf("log2(x%ld)=r%ld\n",x,res);
return(res);
}
而且因为您将要测试这些,
int main()
{
unsigned long int x = 3;
for( x=2; x<1000000000; x*=2 ) {
//printf("x %ld, x+1 %ld, log2(x+1) %d\n",x,x+1,Log2(x+1));
printf("x %ld, x+1 %ld, log2_a(x+1) %d\n",x,x+1,Log2_a(x+1));
printf("x %ld, x+1 %ld, log2_b(x+1) %d\n",x,x+1,Log2_b(x+1));
printf("x %ld, x+1 %ld, log2_c(x+1) %d\n",x,x+1,Log2_c(x+1));
}
return(0);
}
由于这是“另一种”方法,因此将其放入似乎与已经给出的其他方法有所不同。
-1
如果返回x==0
,否则返回floor( log2(x))
(最大结果31)
从32位减少到4位问题,然后使用表。也许优雅,但务实。
__builtin_clz
由于可移植性问题,这就是我不想使用的方式。
为了使其更紧凑,可以改为使用循环来减少,每次增加4到r,最多7次迭代。或某种混合形式,例如(对于64位):循环减少到8,测试减少到4。
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];
}
哇,那是很多答案。我很抱歉回答一个老问题。
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;
}
这个答案与另一个答案非常相似。
1<<k
。口罩呢?(1 << (1<<k-1)-1<< (1<<k-1)
?(most optimal
?您比较最高级吗?)
&
和中有选择地使用的位的值&~
。)您可以用代替十六进制常量((type)1<<(1<<k))-1<<(1<<k)
。
代码:
// 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\n", x, p);
}
或通过设置Y = 1来获取FPU指令FYL2X(Y * Log2 X)的整数部分
[7FFFFFFFFFFFFE00 - 7FFFFFFFFFFFFFFF]
。
另一位发帖人提供了一个使用字节宽查找的查找表。如果您想获得更高的性能(以32K的内存而不是256个查找条目为代价),这是一种使用15位查找表的解决方案,在C#7 for .NET中。
有趣的部分是初始化表。由于这是我们在过程的整个生命周期中都需要的相对较小的块,因此我使用分配了非托管内存Marshal.AllocHGlobal
。如您所见,为了获得最佳性能,整个示例均以本机形式编写:
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;
}
该表需要通过上面的代码进行一次初始化。它是只读的,因此可以共享单个全局副本以进行并发访问。使用此表,您可以快速查找整数log 2,这是我们在此处查找的所有各种整数宽度(8、16、32和64位)。
请注意,表的表项0
(未定义“最高设置位”概念的唯一整数)的值为-1
。这种区别对于正确处理以下代码中的0值高位单词是必要的。事不宜迟,以下是各种整数基元各自的代码:
ulong(64位)版本
/// <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位)版本
/// <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)];
}
上面的各种重载
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个候选人。HighestOne_Tab16A622,496 2个候选人。HighestOne_Tab16C628,234 3名候选人。HighestOne_Tab8A649,146 4个候选人。HighestOne_Tab8B656,847 5个候选人。HighestOne_Tab16B657,147 6名候选人。HighestOne_Tab16D659,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(不安全)760,387 13 _test_B.HighestOne8(不安全)763,904 14 _test_A.HighestOne3(不安全)766433 15 _test_A.HighestOne1(不安全)767,321 16 _test_A.HighestOne4(不安全)771702 17 _test_B.HighestOne2(不安全)772,136 18 _test_B.HighestOne1(不安全)772,527 19 _test_B.HighestOne3(不安全)774,140 20 _test_A.HighestOne7(不安全)774,581 21 _test_B.HighestOne7(不安全)775,463 22 _test_A.HighestOne2(不安全)776,865 23位候选人。HighestOne_NoTab777,698 24 _test_B.HighestOne6(不安全)779,481 25 _test_A.HighestOne6(不安全)781,553 26 _test_B.HighestOne4(不安全)785,504 27 _test_B.HighestOne5(不安全)789,797 28 _test_A.HighestOne0(不安全)809,566 29 _test_B.HighestOne0(不安全)814,990 30 _highest_one_bit.HighestOne 824,345 30 _bitarray_ext.RtlFindMostSignificantBit 894,069 31位候选人。HighestOne_Naive898,865
值得注意的是ntdll.dll!RtlFindMostSignificantBit
via P / Invoke 的糟糕表现 :
[DllImport("ntdll.dll"), SuppressUnmanagedCodeSecurity, SecuritySafeCritical]
public static extern int RtlFindMostSignificantBit(ulong ul);
真的太糟糕了,因为这是整个实际功能:
RtlFindMostSignificantBit:
bsr rdx, rcx
mov eax,0FFFFFFFFh
movzx ecx, dl
cmovne eax,ecx
ret
我无法想象这五行表现不佳,因此必须归咎于托管/本地过渡处罚。我也感到惊讶的是,该测试确实比32 short
字节(和64KB)(16位)直接查找表胜过128字节(和256字节)byte
(8位)查找表。我认为以下内容将与16位查找相比更具竞争力,但后者始终优于此查找:
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];
}
我要指出的最后一件事是,我的deBruijn方法没有取得更好的成绩,我感到非常震惊。这是我以前普遍使用的方法:
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];
}
关于这个SO问题,有很多关于deBruijn方法的优缺点的讨论,我倾向于表示同意。我的猜测是,尽管deBruijn和直接查找表方法(我发现是最快的)都必须进行表查找,并且都具有非常小的分支,但是只有deBruijn才具有64位乘法运算。我只在IndexOfMSB
这里测试了这些功能,而不是deBruijn,IndexOfLSB
但是我希望后者的机会要多得多,因为它的操作少得多(请参见上文),并且我可能会继续将其用于LSB。
我谦虚的方法很简单:
MSB(x)= INT [Log(x)/ Log(2)]
转换:x的最高有效位是(基数x的对数除以基数2的对数)的整数值。
这可以轻松,快速地适应任何编程语言。在计算器上尝试一下,看看它是否有效。
int(math.log((1 << 48) - 1) / math.log(2))
值为
这是在GCC和Clang中工作的C的快速解决方案; 准备复制和粘贴。
#include <limits.h>
unsigned int fls(const unsigned int value)
{
return (unsigned int)1 << ((sizeof(unsigned int) * CHAR_BIT) - __builtin_clz(value) - 1);
}
unsigned long flsl(const unsigned long value)
{
return (unsigned long)1 << ((sizeof(unsigned long) * CHAR_BIT) - __builtin_clzl(value) - 1);
}
unsigned long long flsll(const unsigned long long value)
{
return (unsigned long long)1 << ((sizeof(unsigned long long) * CHAR_BIT) - __builtin_clzll(value) - 1);
}
还有一个针对C ++的改进版本。
#include <climits>
constexpr unsigned int fls(const unsigned int value)
{
return (unsigned int)1 << ((sizeof(unsigned int) * CHAR_BIT) - __builtin_clz(value) - 1);
}
constexpr unsigned long fls(const unsigned long value)
{
return (unsigned long)1 << ((sizeof(unsigned long) * CHAR_BIT) - __builtin_clzl(value) - 1);
}
constexpr unsigned long long fls(const unsigned long long value)
{
return (unsigned long long)1 << ((sizeof(unsigned long long) * CHAR_BIT) - __builtin_clzll(value) - 1);
}
该代码假定value
不会0
。如果要允许0,则需要对其进行修改。
我假设您的问题是整数(以下称为v),而不是无符号整数。
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 & 0x80000000) && 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)。如果我忘了任何事,请告诉我。我没有测试过,但应该可以正常工作。
v <<= 1
是时的未定义行为(UB)v < 0
。
0x8000000
,也许你的意思是那里多了0。