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

时间:2009-06-15 05:30:59

标签: c++ floating-accuracy logarithm

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

代码(C ++):

int targetlevel = int(log(index)/log(2));

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

18 个答案:

答案 0 :(得分:75)

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

#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;
}

答案 1 :(得分:44)

您可以改用此方法:

int targetlevel = 0;
while (index >>= 1) ++targetlevel;

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

最坏情况是当index为0.你可能应该单独检查它并抛出异常或者如果index == 0则返回错误。

答案 2 :(得分:18)

如果你只想要一个快速整数日志 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;
}

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

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

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

顺便说一下,有一些非常快速的黑客可以做到这一点(找到2的补码中设置的最高位)here。我不建议使用它们,除非速度至关重要(我更喜欢可读性)但你应该知道它们存在。

答案 3 :(得分:13)

Base-2整数对数

这是我对64位无符号整数所做的。这将计算base-2对数的最低值,该值等于最高有效位的索引。对于大数字,这种方法吸烟快因为它使用的是一个展开的循环,它始终以log264 = 6步执行。

基本上,它的作用是逐步减去序列{0≤k≤5:2 ^(2 ^ k)} = {2³²,2¹⁶,2⁸,2⁴,2²,2 1} = {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
}

请注意,如果给出无效输入0(这是初始-(n == 0)正在检查的内容),则返回-1。如果您不希望使用n == 0调用它,则可以将int i = 0;替换为初始值设定项,并在函数入口处添加assert(n != 0);

Base-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
}

答案 4 :(得分: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);
}

答案 5 :(得分:3)

我对你正在使用的公式的浮点精度没有任何问题(快速检查数字从1到2 31 - 1发现没有错误),但是如果你担心,你可以使用这个函数,它返回相同的结果,在我的测试中快了66%:

int HighestBit(int i){
    if(i == 0)
        return -1;

    int bit = 31;
    if((i & 0xFFFFFF00) == 0){
        i <<= 24;
        bit = 7;
    }else if((i & 0xFFFF0000) == 0){
        i <<= 16;
        bit = 15;
    }else if((i & 0xFF000000) == 0){
        i <<= 8;
        bit = 23;
    }

    if((i & 0xF0000000) == 0){
        i <<= 4;
        bit -= 4;
    }

    while((i & 0x80000000) == 0){
        i <<= 1;
        bit--;
    }

    return bit; 
}

答案 6 :(得分:2)

可以从 C ++ 20 开始使用

std::bit_width(index) - 1

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

它遵循与the answer provided by Igor Krivokon相同的思想。

答案 7 :(得分:2)

上面有类似的答案。这个答案

  1. 使用64位数字
  2. 让您选择舍入类型和
  3. 包含测试/示例代码
  4. 功能:

    for (int i = 1; i < 35; i++)
      std::cout<<"floorLog2("<<i<<") = "<<floorLog2(i)
               <<", ceilLog2("<<i<<") = "<<ceilLog2(i)<<std::endl;
    

    测试代码:

    {{1}}

答案 8 :(得分:2)

这不是标准的或必然是可移植的,但它通常会起作用。我不知道它有多高效。

将整数索引转换为足够精度的浮点数。假设精度足够,表示将是精确的。

查找IEEE浮点数的表示,提取指数,并进行必要的调整以找到基数2日志。

答案 9 :(得分:2)

int targetIndex = floor(log(i + 0.5)/log(2.0));

答案 10 :(得分:1)

此函数确定表示数字区间所需的位数:[0..maxvalue]。

unsigned binary_depth( unsigned maxvalue )
   {
   int depth=0;
   while ( maxvalue ) maxvalue>>=1, depth++;
   return depth;
   }

通过从结果中减去1,您得到floor(log2(x)),当log2(x)是2的幂时,这是x完全表示。

<强> X ý Y-1
0 0 -1
<强> 1 1 <强> 0
<强> 2 2 <强> 1
3 2 1
4 3 <强> 2
5 3 2
6 3 2
7 3 2
8 4 第3

答案 11 :(得分:1)

如果你正在使用C ++ 11,你可以将它变成constexpr函数:

constexpr std::uint32_t log2(std::uint32_t n)
{
    return (n > 1) ? 1 + log2(n >> 1) : 0;
}

答案 12 :(得分:0)

这个函数我写了here

// The 'i' is for int, there is a log2 for double in stdclib
inline unsigned int log2i( unsigned int x )
{
  unsigned int log2Val = 0 ;
  // Count push off bits to right until 0
  // 101 => 10 => 1 => 0
  // which means hibit was 3rd bit, its value is 2^3
  while( x>>=1 ) log2Val++;  // div by 2 until find log2.  log_2(63)=5.97, so
  // take that as 5, (this is a traditional integer function!)
  // eg x=63 (111111), log2Val=5 (last one isn't counted by the while loop)
  return log2Val ;
}

答案 13 :(得分:0)

这是一篇旧帖子,但我分享了我的一行算法:

unsigned uintlog2(unsigned x)
{
   unsigned l;
   for(l=0; x>1; x>>=1, l++);
   return l;
} 

答案 14 :(得分:0)

重写 Todd Lehman 的回答是更通用的:

#include <climits>

template<typename N>
constexpr N ilog2(N n) {
    N i = 0;
    for (N k = sizeof(N) * CHAR_BIT; 0 < (k /= 2);) {
        if (n >= static_cast<N>(1) << k) { i += k; n >>= k; }
    }
    return i;
}

Clang with -O3展开循环:

0000000100000f50    pushq   %rbp
0000000100000f51    movq    %rsp, %rbp
0000000100000f54    xorl    %eax, %eax
0000000100000f56    cmpl    $0xffff, %edi
0000000100000f5c    setg    %al
0000000100000f5f    shll    $0x4, %eax
0000000100000f62    movl    %eax, %ecx
0000000100000f64    sarl    %cl, %edi
0000000100000f66    xorl    %edx, %edx
0000000100000f68    cmpl    $0xff, %edi
0000000100000f6e    setg    %dl
0000000100000f71    leal    (,%rdx,8), %ecx
0000000100000f78    sarl    %cl, %edi
0000000100000f7a    leal    (%rax,%rdx,8), %eax
0000000100000f7d    xorl    %edx, %edx
0000000100000f7f    cmpl    $0xf, %edi
0000000100000f82    setg    %dl
0000000100000f85    leal    (,%rdx,4), %ecx
0000000100000f8c    sarl    %cl, %edi
0000000100000f8e    leal    (%rax,%rdx,4), %eax
0000000100000f91    xorl    %edx, %edx
0000000100000f93    cmpl    $0x3, %edi
0000000100000f96    setg    %dl
0000000100000f99    leal    (%rdx,%rdx), %ecx
0000000100000f9c    sarl    %cl, %edi
0000000100000f9e    leal    (%rax,%rdx,2), %ecx
0000000100000fa1    xorl    %eax, %eax
0000000100000fa3    cmpl    $0x1, %edi
0000000100000fa6    setg    %al
0000000100000fa9    orl %ecx, %eax
0000000100000fab    popq    %rbp

n为常量时,将在编译时计算结果。

答案 15 :(得分:0)

鉴于浮点数的工作方式(粗略的尾数* 2 ^指数),那么任何2乘以2的幂的2 ^ 127的数字都将准确无误地表示。

这确实提供了一个琐碎但相当棘手的解决方案-将浮点数的位模式解释为整数,然后看一下指数。这是大卫·索恩利(David Thornley)的解决方案。

float f = 1;
for (int i = 0; i < 128; i++)
{
    int x = (*(int*)(&f)>>23) - 127;
    int l = int(log(f) / log(2));

    printf("i = %d, log = %d, f = %f quick = %d\n",
        i, l, f, x);
    f *= 2;
}

任何整数可以表示为浮点数是不正确的-只有位数少于尾数的整数才能表示。在32位浮点数中,这价值23位。

答案 16 :(得分:0)

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

假设您的x> 0

答案 17 :(得分:0)

你的树有多深?您可以设置一个范围说... +/- 0.00000001到该数字,以强制它为整数值。

我实际上不确定你会遇到像1.99999999那样的数字,因为你的log2在计算2 ^ n值时不应该失去任何准确性(因为浮点数舍入到最接近2的幂)。