如何在C++中执行整数log2()?

Pet*_*mit 41 c++ logarithm floating-accuracy

在C++标准库中,我发现只有一个浮点日志方法.现在我使用log来查找二叉树中的索引级别(floor(2log(index))).

代码(C++):

int targetlevel = int(log(index)/log(2));
Run Code Online (Sandbox Code Playgroud)

我担心对于某些边元素(值为2 ^ n的元素),log将返回n-1.999999999999而不是n.0.这种恐惧是否正确?如何修改我的陈述以便始终返回正确的答案?

Mat*_*t J 77

如果您使用的是最新版本的x86或x86-64平台(并且您可能正在使用),请使用bsr将返回无符号整数中最高设置位的位置的指令.事实证明这与log2()完全相同.这是一个bsr使用内联ASM 调用的简短C或C++函数:

#include <stdint.h>
static inline uint32_t log2(const uint32_t x) {
  uint32_t y;
  asm ( "\tbsr %1, %0\n"
      : "=r"(y)
      : "r" (x)
  );
  return y;
}
Run Code Online (Sandbox Code Playgroud)

  • @ user2573802这是错误的.`__builtin_ctz(9)= 0`这不是`log2(9)`. (11认同)
  • 在ARM上你需要clz,它返回31减去你想要的值.GCC有__builtin_clz,可能在x86上使用了bsr. (10认同)
  • 要避免减法,请改用"__builtin_ctz".``int log2(int x){return __builtin_ctz(x);}``它也适用于x86. (3认同)
  • `static inline uint32_t log2(const uint32_t x){return(31 - __builtin_clz(x));}`适用于intel和ARM(但在ARM上有0的错误结果:log2(0)= 4294967295).所以intel的bsr的完整模拟是:`static inline uint32_t log_2(const uint32_t x){if(x == 0)return 0; return(31 - __builtin_clz(x));}` (3认同)
  • @Eddy_Em不确定你对log2(0)的看法是什么,因为从数学上讲,log(0)对于所有基都都是未定义的.返回INT_MAX并不比返回0更"正确". (3认同)
  • 要提一下,如果你使用MSVS,等效的内在函数是[`_BitScanReverse`](http://msdn.microsoft.com/en-US/library/fbxyd7zd%28v=vs.80%29.aspx) (2认同)

Igo*_*kon 45

您可以使用此方法:

int targetlevel = 0;
while (index >>= 1) ++targetlevel;
Run Code Online (Sandbox Code Playgroud)

注意:这将修改索引.如果您不需要它,请创建另一个临时int.

极端情况是当index为0.你可能应该单独检查它并抛出异常或返回错误,如果index == 0.

  • 确保将`index`指定为`unsigned int`,否则你手上有一个非常危险的潜在无限循环错误. (7认同)

pax*_*blo 19

如果您只想要一个快速整数log 2操作,以下函数mylog2()将执行它而不必担心浮点精度:

#include <limits.h>

static unsigned int mylog2 (unsigned int val) {
    if (val == 0) return UINT_MAX;
    if (val == 1) return 0;
    unsigned int ret = 0;
    while (val > 1) {
        val >>= 1;
        ret++;
    }
    return ret;
}

#include <stdio.h>

int main (void) {
    for (unsigned int i = 0; i < 20; i++)
        printf ("%u -> %u\n", i, mylog2(i));
    putchar ('\n');
    for (unsigned int i = 0; i < 10; i++)
        printf ("%u -> %u\n", i+UINT_MAX-9, mylog2(i+UINT_MAX-9));
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

上面的代码也有一个小的测试工具,所以你可以检查行为:

0 -> 4294967295
1 -> 0
2 -> 1
3 -> 1
4 -> 2
5 -> 2
6 -> 2
7 -> 2
8 -> 3
9 -> 3
10 -> 3
11 -> 3
12 -> 3
13 -> 3
14 -> 3
15 -> 3
16 -> 4
17 -> 4
18 -> 4
19 -> 4

4294967286 -> 31
4294967287 -> 31
4294967288 -> 31
4294967289 -> 31
4294967290 -> 31
4294967291 -> 31
4294967292 -> 31
4294967293 -> 31
4294967294 -> 31
4294967295 -> 31
Run Code Online (Sandbox Code Playgroud)

它将返回UINT_MAX输入值0作为未定义结果的指示,因此您应检查的内容(没有有效的无符号整数将具有高的对数).

顺便说一句,有一些疯狂的快速黑客可以从这里做到这一点(找到2的补数中设置的最高位).我不建议使用它们,除非速度至关重要(我更喜欢可读性)但你应该意识到它们存在.

  • paxdiablo - 我喜欢你返回-1输入值为0.注意,你不是*实际上*返回`-1`,而实际上是`~0`(例如,如果你有,则为0xFFFFFFFF) 32位整数),因为你已经声明函数返回`unsigned int`而不是`int`.从这个意义上讲,"〜0"是最接近无穷大的,你可以得到一个整数. (2认同)

Tod*_*man 13

Base-2整数对数

这是我为64位无符号整数所做的.这计算base-2对数的下限,相当于最高位的索引.这种方法对于大数字来说非常快,因为它使用的是一个展开的循环,它始终以log264 = 6步执行.

从本质上讲,它所做的是逐渐减去序列{0≤k≤5:2 ^(2 ^ k)} = {2³²,2¹⁶,2⁸,2⁴,2²,2¹} = {4294967296,65536,256 ,16,4,2,1}并且对减去的值的指数k求和.

int uint64_log2(uint64_t n)
{
  #define S(k) if (n >= (UINT64_C(1) << k)) { i += k; n >>= k; }

  int i = -(n == 0); S(32); S(16); S(8); S(4); S(2); S(1); return i;

  #undef S
}
Run Code Online (Sandbox Code Playgroud)

请注意,如果给出无效输入0(这是初始-(n == 0)检查的内容),则返回-1 .如果您从未期望调用它n == 0,则可以替换int i = 0;初始化程序并assert(n != 0);在函数入口处添加.

基数为10的整数对数

基数为10的整数对数可以使用类似的方法计算 - 最大的平方测试为10 15,因为log 102⁶⁴⁶⁴19.2659......

int uint64_log10(uint64_t n)
{
  #define S(k, m) if (n >= UINT64_C(m)) { i += k; n /= UINT64_C(m); }

  int i = -(n == 0);
  S(16,10000000000000000); S(8,100000000); S(4,10000); S(2,100); S(1,10);
  return i;

  #undef S
}
Run Code Online (Sandbox Code Playgroud)

  • 很漂亮。有了一个像样的编译器和正确的指令集,条件操作可能全部实现为谓词指令,因此不存在分支错误预测;这都是寄存器中以典型现代处理器可以实现的(超标量)速率进行的纯计算。 (2认同)

Zab*_*uza 8

C++20开始,您可以使用

std::bit_width(index) - 1
Run Code Online (Sandbox Code Playgroud)

非常简短、紧凑、快速且可读。

它遵循与Igor Krivokon 提供的答案相同的想法。


zby*_*zek 7

这已在上述评论中提出.使用gcc builtins:

static inline int log2i(int x) {
    assert(x > 0);

    return sizeof(int) * 8 - __builtin_clz(x) - 1;
}

static void test_log2i(void) {
    assert_se(log2i(1) == 0);
    assert_se(log2i(2) == 1);
    assert_se(log2i(3) == 1);
    assert_se(log2i(4) == 2);
    assert_se(log2i(32) == 5);
    assert_se(log2i(33) == 5);
    assert_se(log2i(63) == 5);
    assert_se(log2i(INT_MAX) == sizeof(int)*8-2);
}
Run Code Online (Sandbox Code Playgroud)


Kel*_*mar 6

如果您使用的是 C++11,您可以将其设为 constexpr 函数:

constexpr std::uint32_t log2(std::uint32_t n) noexcept
{
    return (n > 1) ? 1 + log2(n >> 1) : 0;
}
Run Code Online (Sandbox Code Playgroud)