计算(long int) ceiling(log_2(i))
的快速方法是什么,输入和输出是64位整数?有符号或无符号整数的解决方案是可以接受的。我怀疑最好的方法是使用类似于here的方法,而不是尝试我自己的方法,我想使用已经经过充分测试的东西。一般解决方案适用于所有正值。
例如,2,3,4,5,6,7,8的值为1,2,2,3,3,3,3
编辑:到目前为止,最好的路径似乎是使用任意数量的快速现有bithacks或寄存器方法计算整数/楼层日志基数2(MSB的位置),然后如果输入不是,则添加一个两个人的力量。对2的幂的快速按位检查是(n&(n-1))
。
编辑2:整数对数和前导零方法的一个很好的来源是Henry S. Warren在Hacker's Delight中的第5-3和11-4节。这是我发现的最完整的治疗方法。
编辑3:这项技术看起来很有希望:https://stackoverflow.com/a/51351885/365478
答案 0 :(得分:19)
如果你可以限制自己使用gcc,那么有一组内置函数可以返回前导零位的数量,可以用来做一些你需要的工作:
int __builtin_clz (unsigned int x)
int __builtin_clzl (unsigned long)
int __builtin_clzll (unsigned long long)
答案 1 :(得分:19)
此算法已经发布,但以下实现非常紧凑,应优化为无分支代码。
int ceil_log2(unsigned long long x)
{
static const unsigned long long t[6] = {
0xFFFFFFFF00000000ull,
0x00000000FFFF0000ull,
0x000000000000FF00ull,
0x00000000000000F0ull,
0x000000000000000Cull,
0x0000000000000002ull
};
int y = (((x & (x - 1)) == 0) ? 0 : 1);
int j = 32;
int i;
for (i = 0; i < 6; i++) {
int k = (((x & t[i]) == 0) ? 0 : j);
y += k;
x >>= k;
j >>= 1;
}
return y;
}
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
printf("%d\n", ceil_log2(atol(argv[1])));
return 0;
}
答案 2 :(得分:10)
如果您正在为Windows上的64位处理器进行编译,我认为这应该可行。 _BitScanReverse64是一个内在函数。
#include <intrin.h>
__int64 log2ceil( __int64 x )
{
unsigned long index;
if ( !_BitScanReverse64( &index, x ) )
return -1LL; //dummy return value for x==0
// add 1 if x is NOT a power of 2 (to do the ceil)
return index + (x&(x-1)?1:0);
}
对于32位,您可以模拟_BitScanReverse64,对_BitScanReverse进行1或2次调用。 检查x的高32位,((long *)&amp; x)[1],如果需要,检查低32位((long *)&amp; x)[0]。
答案 3 :(得分:5)
#include "stdafx.h"
#include "assert.h"
int getpos(unsigned __int64 value)
{
if (!value)
{
return -1; // no bits set
}
int pos = 0;
if (value & (value - 1ULL))
{
pos = 1;
}
if (value & 0xFFFFFFFF00000000ULL)
{
pos += 32;
value = value >> 32;
}
if (value & 0x00000000FFFF0000ULL)
{
pos += 16;
value = value >> 16;
}
if (value & 0x000000000000FF00ULL)
{
pos += 8;
value = value >> 8;
}
if (value & 0x00000000000000F0ULL)
{
pos += 4;
value = value >> 4;
}
if (value & 0x000000000000000CULL)
{
pos += 2;
value = value >> 2;
}
if (value & 0x0000000000000002ULL)
{
pos += 1;
value = value >> 1;
}
return pos;
}
int _tmain(int argc, _TCHAR* argv[])
{
assert(getpos(0ULL) == -1); // None bits set, return -1.
assert(getpos(1ULL) == 0);
assert(getpos(2ULL) == 1);
assert(getpos(3ULL) == 2);
assert(getpos(4ULL) == 2);
for (int k = 0; k < 64; ++k)
{
int pos = getpos(1ULL << k);
assert(pos == k);
}
for (int k = 0; k < 64; ++k)
{
int pos = getpos( (1ULL << k) - 1);
assert(pos == (k < 2 ? k - 1 : k) );
}
for (int k = 0; k < 64; ++k)
{
int pos = getpos( (1ULL << k) | 1);
assert(pos == (k < 1 ? k : k + 1) );
}
for (int k = 0; k < 64; ++k)
{
int pos = getpos( (1ULL << k) + 1);
assert(pos == k + 1);
}
return 0;
}
答案 4 :(得分:5)
使用@egosys提到的gcc builtins,你可以构建一些有用的宏。 对于快速粗糙的楼层(log2(x))计算,您可以使用:
#define FAST_LOG2(x) (sizeof(unsigned long)*8 - 1 - __builtin_clzl((unsigned long)(x)))
对于类似的ceil(log2(x)),请使用:
#define FAST_LOG2_UP(x) (((x) - (1 << FAST_LOG2(x))) ? FAST_LOG2(x) + 1 : FAST_LOG2(x))
后者可以使用更多的gcc特性进一步优化,以避免对内置的双重调用,但我不确定你是否需要它。
答案 5 :(得分:4)
以下代码片段是一种安全且可移植的方法,可以扩展使用支持编译器(Clang)编译的普通C方法(如@ dgobbi)来使用编译器内在函数。将其置于方法的顶部将导致该方法在可用时使用内置函数。当内置函数不可用时,该方法将回退到标准C代码。
#ifndef __has_builtin
#define __has_builtin(x) 0
#endif
#if __has_builtin(__builtin_clzll) //use compiler if possible
return ((sizeof(unsigned long long) * 8 - 1) - __builtin_clzll(x)) + (!!(x & (x - 1)));
#endif
答案 6 :(得分:4)
我知道的最快的方法是使用快速log2
进行四舍五入,将输入值前后的无条件调整组合起来以处理舍入情况,如下所示。lg_down()
。
/* base-2 logarithm, rounding down */
static inline uint64_t lg_down(uint64_t x) {
return 63U - __builtin_clzl(x);
}
/* base-2 logarithm, rounding up */
static inline uint64_t lg_up(uint64_t x) {
return lg_down(x - 1) + 1;
}
对于除精确的2的幂之外的所有值,对舍入结果基本加1已经是正确的(因为在这种情况下,floor
和ceil
方法应返回相同的答案),因此足以从输入值中减去1来处理这种情况(对于其他情况,它不会改变答案)并将结果加1。
这通常比通过显式检查2的幂次幂(例如,添加!!(x & (x - 1))
项)来调整值的方法要快一些。它避免了任何比较和条件运算或分支,更可能仅在进行内联时更容易进行矢量化处理,等等。
这依赖于大多数CPU使用内置__builtin_clzl
的clang / icc / gcc提供的“计数前导位”功能,但是其他平台也提供了类似的功能(例如,Visual Studio中的BitScanReverse
固有的) )。
不幸的是,许多人为log(1)
返回了错误的答案,因为这导致了__builtin_clzl(0)
,根据gcc文档,这是未定义的行为。当然,常规的“计数前导零”功能已将行为完美地定义为零,但是以这种方式定义了gcc内置函数,因为在x86上进行BMI ISA扩展之前,它将一直使用bsr
instruction行为不确定。
如果您知道直接使用lzcnt
内部函数拥有lzcnt
指令,则可以解决此问题。除x86之外的大多数平台都从来没有遇到过bsr
错误,并且如果有的话,可能还提供了访问其“计数前零”指令的方法。
答案 7 :(得分:3)
true 最快的解决方案:
63个条目的二叉搜索树。这些是从0到63的2的幂。用于创建树的一次性生成函数。叶子代表权力的对数基数2(基本上是数字1-63)。
要查找答案,请将数字输入树中,然后导航到大于项目的叶节点。如果叶节点完全相等,则结果为叶值。否则,结果是叶值+ 1。
复杂性固定为O(6)。
答案 8 :(得分:2)
查找具有整数输出的整数(64位或任何其他位)的日志基数2等同于查找设置的最高有效位。为什么?因为日志库2是你可以将数字除以2到达1的次数。
找到设置的MSB的一种方法是简单地每次向右移位1,直到你有0.另一种更有效的方法是通过位掩码进行某种二进制搜索。
通过检查是否设置了除MSB以外的任何其他位,可以很容易地计算出ceil部分。
答案 9 :(得分:1)
如果您有80位或128位浮点数,则转换为该类型,然后读取指数位。此链接具有详细信息(对于最多52位的整数)和其他几种方法:
http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogIEEE64Float
另外,检查ffmpeg源。我知道他们有一个非常快的算法。即使它不能直接扩展到更大的尺寸,您也可以轻松地执行if (x>INT32_MAX) return fastlog2(x>>32)+32; else return fastlog2(x);
答案 10 :(得分:1)
天真线性搜索可以是均匀分布整数的选项,因为它平均需要略少于2次比较(对于任何整数大小)。
/* between 1 and 64 comparisons, ~2 on average */
#define u64_size(c) ( \
0x8000000000000000 < (c) ? 64 \
: 0x4000000000000000 < (c) ? 63 \
: 0x2000000000000000 < (c) ? 62 \
: 0x1000000000000000 < (c) ? 61 \
...
: 0x0000000000000002 < (c) ? 2 \
: 0x0000000000000001 < (c) ? 1 \
: 0 \
)
答案 11 :(得分:1)
在撰写本文时,我将为您提供最快的x86-64方式,如果您愿意为<2> 63自变量<2 ^ 63 工作的话,请提供一种通用技术。完整范围,然后请参阅下文。
我对其他答案的质量很低感到惊讶,因为它们告诉您如何获得地板,但是以非常昂贵的方式(使用条件和一切!)将地板转换为天花板。
例如,如果您可以使用__builtin_clzll
快速获得对数的下限,则很容易这样获得下限:
unsigned long long log2Floor(unsigned long long x) {
return 63 - __builtin_clzll(x);
}
unsigned long long log2Ceiling(unsigned long long x) {
return log2Floor(2*x - 1);
}
之所以有效,是因为它向结果加1,除非x恰好是2的幂。
请参见x86-64汇编器差异at the compiler explorer,以了解有关天花板的另一种实现方式:
auto log2CeilingDumb(unsigned long x) {
return log2Floor(x) + (!!(x & (x - 1)));
}
礼物:
log2Floor(unsigned long): # @log2Floor(unsigned long)
bsr rax, rdi
ret
log2CeilingDumb(unsigned long): # @log2CeilingDumb(unsigned long)
bsr rax, rdi
lea rcx, [rdi - 1]
and rcx, rdi
cmp rcx, 1
sbb eax, -1
ret
log2Ceiling(unsigned long): # @log2Ceiling(unsigned long)
lea rax, [rdi + rdi]
add rax, -1
bsr rax, rax
ret
对于完整范围,它在先前的答案中:return log2Floor(x - 1) + 1
,因为它在x86-64中使用了四个指令,而在上面的三个指令中却大大降低了速度。
答案 12 :(得分:1)
我已经对64位“最高位”的几种实现进行了基准测试。实际上,最“无分支”的代码并不是最快的。
这是我的highest-bit.c
源文件:
int highest_bit_unrolled(unsigned long long n)
{
if (n & 0xFFFFFFFF00000000) {
if (n & 0xFFFF000000000000) {
if (n & 0xFF00000000000000) {
if (n & 0xF000000000000000) {
if (n & 0xC000000000000000)
return (n & 0x8000000000000000) ? 64 : 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_bs(unsigned long long n)
{
const unsigned 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;
}
int highest_bit_shift(unsigned long long n)
{
int i = 0;
for (; n; n >>= 1, i++)
; /* empty */
return i;
}
static int count_ones(unsigned long long d)
{
d = ((d & 0xAAAAAAAAAAAAAAAA) >> 1) + (d & 0x5555555555555555);
d = ((d & 0xCCCCCCCCCCCCCCCC) >> 2) + (d & 0x3333333333333333);
d = ((d & 0xF0F0F0F0F0F0F0F0) >> 4) + (d & 0x0F0F0F0F0F0F0F0F);
d = ((d & 0xFF00FF00FF00FF00) >> 8) + (d & 0x00FF00FF00FF00FF);
d = ((d & 0xFFFF0000FFFF0000) >> 16) + (d & 0x0000FFFF0000FFFF);
d = ((d & 0xFFFFFFFF00000000) >> 32) + (d & 0x00000000FFFFFFFF);
return d;
}
int highest_bit_parallel(unsigned long long n)
{
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
n |= n >> 32;
return count_ones(n);
}
int highest_bit_so(unsigned long long x)
{
static const unsigned long long t[6] = {
0xFFFFFFFF00000000ull,
0x00000000FFFF0000ull,
0x000000000000FF00ull,
0x00000000000000F0ull,
0x000000000000000Cull,
0x0000000000000002ull
};
int y = (((x & (x - 1)) == 0) ? 0 : 1);
int j = 32;
int i;
for (i = 0; i < 6; i++) {
int k = (((x & t[i]) == 0) ? 0 : j);
y += k;
x >>= k;
j >>= 1;
}
return y;
}
int highest_bit_so2(unsigned long long value)
{
int pos = 0;
if (value & (value - 1ULL))
{
pos = 1;
}
if (value & 0xFFFFFFFF00000000ULL)
{
pos += 32;
value = value >> 32;
}
if (value & 0x00000000FFFF0000ULL)
{
pos += 16;
value = value >> 16;
}
if (value & 0x000000000000FF00ULL)
{
pos += 8;
value = value >> 8;
}
if (value & 0x00000000000000F0ULL)
{
pos += 4;
value = value >> 4;
}
if (value & 0x000000000000000CULL)
{
pos += 2;
value = value >> 2;
}
if (value & 0x0000000000000002ULL)
{
pos += 1;
value = value >> 1;
}
return pos;
}
这是highest-bit.h
:
int highest_bit_unrolled(unsigned long long n);
int highest_bit_bs(unsigned long long n);
int highest_bit_shift(unsigned long long n);
int highest_bit_parallel(unsigned long long n);
int highest_bit_so(unsigned long long n);
int highest_bit_so2(unsigned long long n);
和主程序(抱歉所有复制和粘贴):
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "highest-bit.h"
double timedelta(clock_t start, clock_t end)
{
return (end - start)*1.0/CLOCKS_PER_SEC;
}
int main(int argc, char **argv)
{
int i;
volatile unsigned long long v;
clock_t start, end;
start = clock();
for (i = 0; i < 10000000; i++) {
for (v = 0x8000000000000000; v; v >>= 1)
highest_bit_unrolled(v);
}
end = clock();
printf("highest_bit_unrolled = %6.3fs\n", timedelta(start, end));
start = clock();
for (i = 0; i < 10000000; i++) {
for (v = 0x8000000000000000; v; v >>= 1)
highest_bit_parallel(v);
}
end = clock();
printf("highest_bit_parallel = %6.3fs\n", timedelta(start, end));
start = clock();
for (i = 0; i < 10000000; i++) {
for (v = 0x8000000000000000; v; v >>= 1)
highest_bit_bs(v);
}
end = clock();
printf("highest_bit_bs = %6.3fs\n", timedelta(start, end));
start = clock();
for (i = 0; i < 10000000; i++) {
for (v = 0x8000000000000000; v; v >>= 1)
highest_bit_shift(v);
}
end = clock();
printf("highest_bit_shift = %6.3fs\n", timedelta(start, end));
start = clock();
for (i = 0; i < 10000000; i++) {
for (v = 0x8000000000000000; v; v >>= 1)
highest_bit_so(v);
}
end = clock();
printf("highest_bit_so = %6.3fs\n", timedelta(start, end));
start = clock();
for (i = 0; i < 10000000; i++) {
for (v = 0x8000000000000000; v; v >>= 1)
highest_bit_so2(v);
}
end = clock();
printf("highest_bit_so2 = %6.3fs\n", timedelta(start, end));
return 0;
}
我已经尝试过各种新旧的Intel x86盒。
highest_bit_unrolled
(展开的二进制搜索)始终比highest_bit_parallel
(无分支位操作)快得多。这比highest_bit_bs
(二进制搜索循环)要快,而比highest_bit_shift
(朴素的移位和计数循环)要快。
highest_bit_unrolled
也比接受的SO答案(highest_bit_so
)和另一个答案(highest_bit_so2
)中的答案要快。
基准测试循环通过覆盖连续位的一位掩码。这是为了克服展开的二进制搜索中的分支预测,这是现实的:在现实世界的程序中,输入的情况不太可能表现出位位置的局部性。
以下是旧Intel(R) Core(TM)2 Duo CPU E4500 @ 2.20GHz
上的结果:
$ ./highest-bit
highest_bit_unrolled = 6.090s
highest_bit_parallel = 9.260s
highest_bit_bs = 19.910s
highest_bit_shift = 21.130s
highest_bit_so = 8.230s
highest_bit_so2 = 6.960s
在较新的型号Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz
上:
highest_bit_unrolled = 1.555s
highest_bit_parallel = 3.420s
highest_bit_bs = 6.486s
highest_bit_shift = 9.505s
highest_bit_so = 4.127s
highest_bit_so2 = 1.645s
在较新的硬件上,highest_bit_so2
在较新的硬件上更接近highest_bit_unrolled
。顺序不完全相同;现在highest_bit_so
确实落后了,并且比highest_bit_parallel
慢。
最快的highest_bit_unrolled
包含最多代码和最多分支。每个单独的返回值都可以通过一组不同的条件使用自己的专用代码来达到。
“避免所有分支”的直觉(由于担心分支错误的预测)并不总是正确的。现代的(甚至不再是现代的)处理器包含相当多的狡猾,以免受到分支的影响。
P.S。 highest_bit_unrolled
是用December 2011的TXR语言引入的(由于调试,有错误)。
最近,我开始怀疑是否有些更好,更紧凑的无分支代码可能不会更快。
结果令我有些惊讶。
可以说,对于GNU C,该代码实际上应该是#ifdef
并使用一些编译器原语,但是就可移植性而言,该版本仍然存在。
答案 13 :(得分:1)
一种对最高位的二进制搜索。
发布在 stanford.edu 上的有关按位运算技巧的很好参考:Bit Twiddling Hacks。 该参考文献中有很多关于许多事情的示例,包括用于计算 log2 值的几种算法。这包括与此类似的——也许更好的——方法。
我有一个想法,对最高有效位的二进制搜索将比简单解决方案的最大 60+ 循环快。该算法在每次迭代中将 n
移动其最后一次移动值的一半,在 msb 上为零。
对于 64 位值,需要 6 次迭代来确定最高有效位的位置 - 即 n
的下限 log2。
根据我的测量,这种方法的性能并不比下面第二个示例中的简单循环好多少。
// floor log2 in 6 max iterations for unsigned 64-bit values.
//
int floor_log2(unsigned long long n)
{
int shval = 32;
int msb = 0;
while (shval) {
if (n >> shval) {
msb += shval;
n >>= shval;
}
shval >>= 1;
}
return msb;
}
int ceil_log2(unsigned long long n)
{
return floor_log2(2 * n - 1);
}
简单算法:
int floor_log2_simple(unsigned long long n)
{
int c = 0;
while (n) {
n >>= 1;
c++;
}
return c - 1;
}
这些在 Rust 中实现的相同算法的性能几乎相同。顶级方法的性能要好一点——但平均只有几纳秒。对算法进行多少调整都无法带来更好的性能。
嗯...
答案 14 :(得分:0)
下面的代码更简单,只要输入x&gt; = 1就可以使用。输入clog2(0)将得到一个未定义的答案(这是有道理的,因为log(0)是无穷大...)你可以添加如果你想要错误检查(x == 0):
unsigned int clog2 (unsigned int x)
{
unsigned int result = 0;
--x;
while (x > 0) {
++result;
x >>= 1;
}
return result;
}
顺便说一句,log2的底层代码类似:(再次,假设x> = 1)
unsigned int flog2 (unsigned int x)
{
unsigned int result = 0;
while (x > 1) {
++result;
x >>= 1;
}
return result;
}