一个很棒的编程资源,Bit Twiddling Hacks,提出了下面的方法来计算一个32位整数的log2:
#define LT(n) n, n, n, n, n, n, n, n, n, n, n, n, n, n, n, n
static const char LogTable256[256] =
{
-1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
LT(4), LT(5), LT(5), LT(6), LT(6), LT(6), LT(6),
LT(7), LT(7), LT(7), LT(7), LT(7), LT(7), LT(7), LT(7)
};
unsigned int v; // 32-bit word to find the log of
unsigned r; // r will be lg(v)
register unsigned int t, tt; // temporaries
if (tt = v >> 16)
{
r = (t = tt >> 8) ? 24 + LogTable256[t] : 16 + LogTable256[tt];
}
else
{
r = (t = v >> 8) ? 8 + LogTable256[t] : LogTable256[v];
}
并提到
查找表方法只需要大约7次运算就可以找到32位值的对数,如果扩展到64位,则需要大约9次运算。
但是,遗憾的是,没有给予任何额外的信息,说明应该如何将算法扩展到64位整数。
有没有关于这种64位算法的提示?
9条答案
按热度按时间vyu0f0g11#
内部函数确实很快,但仍然不足以实现真正的跨平台、独立于编译器的log2。因此,如果有人感兴趣,这里是我在自己研究这个主题时遇到的最快、无分支、CPU抽象的DeBruijn类算法。
向下舍入到2的下一个较低幂的部分取自Power-of-2 Boundaries,获取尾随零的数量的部分取自BitScan(
(bb & -bb)
代码用于挑出设置为1的最右边的位,在我们将值向下舍入到2的下一个幂之后,就不需要该位了)。顺便说一下,32位实现是
与任何其他计算方法一样,log2要求输入值大于零。
5ssjco0h2#
如果您使用的是GCC,则在这种情况下不需要查找表。
GCC提供了一个内置函数来确定前导零的数量:
内置功能:
int __builtin_clz (unsigned int x)
返回x中从最高有效位位置开始的前导0位数。如果x为0,则结果未定义。
因此,您可以定义:
并且它对任何unsigned long long int都有效。结果向下舍入。
对于x86和AMD64,GCC会把它编译成一条
bsr
指令,所以解决的速度非常快(比查找表快得多)。Working example:
编译输出:https://godbolt.org/z/16GnjszMs
eiee3dmh3#
我试图通过对幻数进行暴力处理,将带有乘法和查找的O(lg(N))操作中的N位整数的以2为底的对数转换为64位,不用说,这需要一段时间。
然后我找到了Desmond的答案,并决定尝试他的神奇数字作为起点。因为我有一个6核处理器,我并行运行它开始0x 07 EDD 5E 59 A4 E28 C2/ 6倍。我很惊讶它立即找到了一些东西。原来0x 07 EDD 5E 59 A4 E28 C2/ 2工作。
下面是0x 07 EDD 5E 59 A4 E28 C2的代码,它节省了移位和减法操作:
o2gm4chl4#
以2为底的整数对数
下面是我对64位无符号整数所做的,计算以2为底的对数的下限,它相当于最高有效位的下标,这个方法对大数来说是“非常快”的,因为它使用了一个展开的循环,总是以log 64 = 6个步骤执行。
本质上,它所做的是减去序列{ 0 ≤ k ≤ 5:2^(2^k)} = { 2³²,2¹,2,2 ²,2¹ } = { 4294967296,65536,256,16,4,2,1 },并将相减后的值的指数k相加。
注意,如果给定了无效的0输入(这是初始化
-(n == 0)
所检查的),则返回-1。如果您从未期望用n == 0
调用它,则可以用int i = 0;
替换初始化器,并在函数入口处添加assert(n != 0);
。以10为底的整数对数
以10为底的整数对数可以用类似的-计算,要测试的最大平方是10¹,因为log 2 19.2659...(注:这并不是完成以10为底的整数对数的最快方法,因为它使用整数除法,这本身就很慢,一个更快的实现是使用一个累加器,其值按指数增长,然后与累加器进行比较,实际上是进行一种二进制搜索。)
c90pui9n5#
下面是一个非常紧凑 * 和 * 快速的扩展,没有使用额外的临时变量:
下面是一个用
if
链构建的示例,同样没有使用额外的临时变量,但可能不是最快的。jaql4c8m6#
如果你在寻找c的答案,你会得到这里,因为它归结为计数零,那么你会得到
std::countl_zero
,根据godbolt.org被称为bsr
。std::countl_zero
可以从C20中获得,你可能需要在编译器命令行中添加-std=gnu++2a
hzbexzde7#
该算法基本上找出哪个字节包含最高有效1位,然后在查找表中查找该字节以找到该字节的日志,然后将其添加到该字节的位置。
以下是32位算法的简化版本:
这是等效的64位算法:
我想出了一个算法,任何大小的类型,我认为是比原来的更好。
注意:
b = sizeof(v) << 2
将B设置为v中位数的一半。我在这里使用了移位而不是乘法(只是因为我喜欢这样)。您可以向该算法添加一个查找表,以尽可能地加快它的速度,但它更多的是一个概念验证。
5hcedyr08#
拿着这个
工作原理:输入整数
x
被强制转换为float
,然后重新解释为位。IEEEfloat
将位30 - 23中的指数存储为偏移量为127的整数,因此将其右移23位并减去偏移量,得到log2对于64位整数输入,x
被强制转换为double
,对于double
,指数在位62 - 52中(向右移位52位)并且指数偏移为1023。chy5wohz9#
这是SPWorley on 3/22/2009的修改版本
支持〉55位,0返回0。要使其返回负数而不是0,请删除"|1 "。
一个测试函数和输出...