找出一个数字在 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 (清除前导零)计算前导零的数量。您必须测试一下 clzclzlclzll 中的哪一个对应于您的 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 中)的主要内容,如果未能解决你的问题,请参考以下文章

剑指offer_查找数组中的任一重复元素

数组中重复的数字 --剑指offer

找出数组中重复的数字(c语言)

范围内的指数幂[重复]

剑指 Offer 53

0~n-1中缺失的数字