找出一个数字在 2 范围内的哪个幂? (在 C 中)
Posted
技术标签:
【中文标题】找出一个数字在 2 范围内的哪个幂? (在 C 中)【英文标题】:Find which power of 2 range a number falls within? (In C) 【发布时间】:2012-08-14 18:07:32 【问题描述】:如它是否在 2^3 - 2^4、2^4 - 2^5 等范围内。返回的数字将是 EXPONENT 本身(减去偏移量)。
如何才能尽可能快速高效地完成这项工作?这个函数会在一个极度依赖速度的程序中被调用很多次。这是我当前的代码,但由于它使用 for 循环,效率太低了。
static inline size_t getIndex(size_t numOfBytes)
int i = 3;
for (; i < 32; i++)
if (numOfBytes < (1 << i))
return i - OFFSET;
return (NUM_OF_BUCKETS - 1);
非常感谢!
【问题讨论】:
使用 reversebitscan 内部函数。 【参考方案1】:据我所知,您所追求的只是 log2(n)。
如果您的目标架构具有可以执行此操作的指令,则可能值得作弊并使用一些内联汇编。有关硬件支持的大量讨论和信息,请参阅“查找第一组”中的 Wikipedia entry。
【讨论】:
编译器也可能支持直接实现这一点。例如 gcc 有__builtin_clz
(清除前导零)计算前导零的数量。您必须测试一下 clz
、clzl
或 clzll
中的哪一个对应于您的 size_t
类型,但它应该只为此发出一条汇编指令。【参考方案2】:
一种方法是找到设置为 1 的最高位。不过,我正在尝试考虑这是否有效,因为在最坏的情况下您仍然需要进行 n 次检查。
也许你可以做一个二分搜索风格,检查它是否大于 2^16,如果是,检查它是否大于 2^24(假设这里是 32 位),如果不是,然后检查它是否大于2^20 等...这将是 log(n) 检查,但我不确定位检查与完整 int 比较的效率。
都可以获得一些性能数据。
【讨论】:
有没有办法在你获胜之前不右移? @arasmussen:是的,使用任何算法来测试一个数字是否是 2 的幂,您可以稍作修改使用它。像你说的那样计算所需的位数可能最简单。 啊……二进制排序。忘记了。 @John,最快的方法通常是使用编译器内置函数,它可以利用本机 CPU 指令。【参考方案3】:在 Sean Eron Anderson 的出色 Bit Twiddling Hacks 页面中描述了一种使用 de Bruijn 序列的特别有效的算法:
uint32_t v; // find the log base 2 of 32-bit v
int r; // result goes here
static const int MultiplyDeBruijnBitPosition[32] =
0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30,
8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31
;
v |= v >> 1; // first round down to one less than a power of 2
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
r = MultiplyDeBruijnBitPosition[(uint32_t)(v * 0x07C4ACDDU) >> 27];
它可以在没有分支的情况下进行 13 次操作!
【讨论】:
【参考方案4】:您基本上是在尝试计算:floor(log2(x))
取以 2 为底的对数,然后取地板。
在 C 中最便携的方法是使用 logf()
函数,它找到日志到基 e,然后调整:log2(x) == logf(x) / logf(2.0)
在此处查看答案:How to write log base(2) in c/c++
如果您只是将生成的浮点值转换为 int
,则同时计算 floor()
。
但是,如果它可供您使用并且您可以使用它,那么有一种非常快速的方法可以计算浮点数的log2()
:logbf()
来自手册页:
The inte-
ger constant FLT_RADIX, defined in <float.h>, indicates the radix used
for the system's floating-point representation. If FLT_RADIX is 2,
logb(x) is equal to floor(log2(x)), except that it is probably faster.
http://linux.die.net/man/3/logb
如果您考虑浮点数的存储方式,您会意识到值floor(log2(x))
是数字的一部分,如果您只是提取该值,您就完成了。一点点移位和位掩码,并从指数(或技术上的“有效数字”)中减去偏差,你就有了。为任何浮点值 x
计算 floor(log2(x))
的最快方法。
http://en.wikipedia.org/wiki/Single_precision
但实际上logbf()
在将结果提供给您之前将其转换为浮点数,并处理错误。如果您编写自己的函数来将指数提取为整数,它会稍微快一些,并且整数就是您想要的。如果您想编写自己的函数,则需要使用 C union
来访问浮点数内的位;尝试使用指针会给您带来与“类型双关语”相关的警告或错误,至少在 GCC 上是这样。如果您询问,我将详细说明如何执行此操作。我以前写过这段代码,作为inline
函数。
如果您只有一小部分数字要测试,您可以将数字转换为整数,然后使用查找表。
【讨论】:
对于 x=536870911,logf(x) / logf(2.0)
返回 29 但应该返回 28。整数 x 传递给 logf 时,会转换为浮点数,无法准确表示,因此进行了四舍五入。
logbf 取决于浮点基数。通常使用 IEEE 754 二进制浮点数,基数为 2。但是,标准 C 函数 frexp 保证使用底数为 2 的指数,因此它在 logbf 不是的情况下是可移植的。
如果536870911
是必须正常工作的输入之一,那么我猜应该调用logb()
函数而不是logbf()
(使用double
值)。很明显,如果基数不是 2,则需要完全使用另一种解决方案。我不相信我曾经使用过没有基数 2 浮点值的计算机。你?那会是什么?
是的,我使用过几台不使用 base 2 的计算机,例如 Xerox Sigma 9、VAX-11、IBM System/360 和 HP-3000。在 IEEE 754 之前,base 16 并不少见。今天,base 10 仍在某些系统上使用。在我之前,有一台 base 3 计算机。【参考方案5】:
你可以使用浮点数表示:
double n_bytes = numOfBytes
取指数位应该会给你结果,因为浮点数表示为:
(-1)^S X (1. + M) X 2^E
在哪里: S - 标志 M - 尾数 E - 指数
要构造掩码和移位,您必须阅读您正在使用的浮点类型的确切位模式。
CPU 浮点支持为您完成了大部分工作。
更好的方法是使用内置函数:
double frexp (double x, int * exp );
Floating point representation
【讨论】:
没有必要深入研究浮点格式。标准 C 函数 frexp 提供了必要的值。【参考方案6】:#include <Limits.h> // For CHAR_BIT.
#include <math.h> // For frexp.
#include <stdio.h> // For printing results, as a demonstration.
// These routines assume 0 < x.
/* This requires GCC (or any other compiler that supplies __builtin_clz). It
should perform well on any machine with a count-leading-zeroes instruction
or something similar.
*/
static int log2A(unsigned int x)
return sizeof x * CHAR_BIT - 1 - __builtin_clz(x);
/* This requires that a double be able to exactly represent any unsigned int.
(This is true for 32-bit integers and 64-bit IEEE 754 floating-point.) It
might perform well on some machines and poorly on others.
*/
static int log2B(unsigned int x)
int exponent;
frexp(x, &exponent);
return exponent - 1;
int main(void)
// Demonstrate the routines.
for (unsigned int x = 1; x; x <<= 1)
printf("0x%08x: log2A -> %2d, log2B -> %2d.\n", x, log2A(x), log2B(x));
return 0;
【讨论】:
【参考方案7】:这在任何具有硬件浮点单元的机器上通常都很快:
((union float val; uint32_t repr; ) x .repr >> 23) - 0x7f
它所做的唯一假设是浮点是 IEEE 并且整数和浮点字节序匹配,这两者在基本上所有现实世界的系统(当然是所有现代系统)上都是正确的。
编辑:当我过去使用它时,我不需要它来处理大量数据。 Eric 指出,对于不适合浮点数的整数,它会给出错误的结果。这是一个修订版(尽管可能较慢),它修复了该问题并支持高达 52 位的值(特别是所有 32 位正整数输入):
((union double val; uint64_t repr; ) x .repr >> 52) - 0x3ff
另外请注意,我假设x
是一个正数(不仅仅是非负数,也非零数)。如果x
是负数,你会得到一个虚假的结果,如果x
是0,你会得到一个很大的负数结果(近似负无穷大作为对数)。
【讨论】:
对于33554431,这个返回25,但应该返回24。整数x用于初始化val时,转换为float,不能准确表示,所以取整。跨度>以上是关于找出一个数字在 2 范围内的哪个幂? (在 C 中)的主要内容,如果未能解决你的问题,请参考以下文章