What is the fastest/most efficient way to find the highest set bit (msb) in an integer in C?

后端 未结 27 2724
终归单人心
终归单人心 2020-11-22 03:35

If I have some integer n, and I want to know the position of the most significant bit (that is, if the least significant bit is on the right, I want to know the position of

相关标签:
27条回答
  • 2020-11-22 04:12

    A version in C using successive approximation:

    unsigned int getMsb(unsigned int n)
    {
      unsigned int msb  = sizeof(n) * 4;
      unsigned int step = msb;
      while (step > 1)
     {
        step /=2;
        if (n>>msb)
         msb += step;
       else
         msb -= step;
     }
      if (n>>msb)
        msb++;
      return (msb - 1);
    }
    

    Advantage: the running time is constant regardless of the provided number, as the number of loops are always the same. ( 4 loops when using "unsigned int")

    0 讨论(0)
  • 2020-11-22 04:14

    Some overly complex answers here. The Debruin technique should only be used when the input is already a power of two, otherwise there's a better way. For a power of 2 input, Debruin is the absolute fastest, even faster than _BitScanReverse on any processor I've tested. However, in the general case, _BitScanReverse (or whatever the intrinsic is called in your compiler) is the fastest (on certain CPU's it can be microcoded though).

    If the intrinsic function is not an option, here is an optimal software solution for processing general inputs.

    u8  inline log2 (u32 val)  {
        u8  k = 0;
        if (val > 0x0000FFFFu) { val >>= 16; k  = 16; }
        if (val > 0x000000FFu) { val >>= 8;  k |= 8;  }
        if (val > 0x0000000Fu) { val >>= 4;  k |= 4;  }
        if (val > 0x00000003u) { val >>= 2;  k |= 2;  }
        k |= (val & 2) >> 1;
        return k;
    }
    

    Note that this version does not require a Debruin lookup at the end, unlike most of the other answers. It computes the position in place.

    Tables can be preferable though, if you call it repeatedly enough times, the risk of a cache miss becomes eclipsed by the speedup of a table.

    u8 kTableLog2[256] = {
    0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
    5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
    6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
    6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
    7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
    7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
    7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
    7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
    };
    
    u8 log2_table(u32 val)  {
        u8  k = 0;
        if (val > 0x0000FFFFuL) { val >>= 16; k  = 16; }
        if (val > 0x000000FFuL) { val >>=  8; k |=  8; }
        k |= kTableLog2[val]; // precompute the Log2 of the low byte
    
        return k;
    }
    

    This should produce the highest throughput of any of the software answers given here, but if you only call it occasionally, prefer a table-free solution like my first snippet.

    0 讨论(0)
  • 2020-11-22 04:15

    This is sort of like finding a kind of integer log. There are bit-twiddling tricks, but I've made my own tool for this. The goal of course is for speed.

    My realization is that the CPU has an automatic bit-detector already, used for integer to float conversion! So use that.

    double ff=(double)(v|1);
    return ((*(1+(uint32_t *)&ff))>>20)-1023;  // assumes x86 endianness
    

    This version casts the value to a double, then reads off the exponent, which tells you where the bit was. The fancy shift and subtract is to extract the proper parts from the IEEE value.

    It's slightly faster to use floats, but a float can only give you the first 24 bit positions because of its smaller precision.


    To do this safely, without undefined behaviour in C++ or C, use memcpy instead of pointer casting for type-punning. Compilers know how to inline it efficiently.

    // static_assert(sizeof(double) == 2 * sizeof(uint32_t), "double isn't 8-byte IEEE binary64");
    // and also static_assert something about FLT_ENDIAN?
    
    double ff=(double)(v|1);
    
    uint32_t tmp;
    memcpy(&tmp, ((const char*)&ff)+sizeof(uint32_t), sizeof(uint32_t));
    return (tmp>>20)-1023;
    

    Or in C99 and later, use a union {double d; uint32_t u[2];};. But note that in C++, union type punning is only supported on some compilers as an extension, not in ISO C++.


    This will usually be slower than a platform-specific intrinsic for a leading-zeros counting instruction, but portable ISO C has no such function. Some CPUs also lack a leading-zero counting instruction, but some of those can efficiently convert integers to double. Type-punning an FP bit pattern back to integer can be slow, though (e.g. on PowerPC it requires a store/reload and usually causes a load-hit-store stall).

    This algorithm could potentially be useful for SIMD implementations, because fewer CPUs have SIMD lzcnt. x86 only got such an instruction with AVX512CD

    0 讨论(0)
  • 2020-11-22 04:16

    The code:

        // x>=1;
        unsigned func(unsigned x) {
        double d = x ;
        int p= (*reinterpret_cast<long long*>(&d) >> 52) - 1023;
        printf( "The left-most non zero bit of %d is bit %d\n", x, p);
        }
    

    Or get the integer part of FPU instruction FYL2X (Y*Log2 X) by setting Y=1

    0 讨论(0)
  • 2020-11-22 04:17

    thats some kind of binary search, it works with all kinds of (unsigned!) integer types

    #include <climits>
    #define UINT (unsigned int)
    #define UINT_BIT (CHAR_BIT*sizeof(UINT))
    
    int msb(UINT x)
    {
        if(0 == x)
            return -1;
    
        int c = 0;
    
        for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
        if(static_cast<UINT>(x >> i))
        {
            x >>= i;
            c |= i;
        }
    
        return c;
    }
    

    to make complete:

    #include <climits>
    #define UINT unsigned int
    #define UINT_BIT (CHAR_BIT*sizeof(UINT))
    
    int lsb(UINT x)
    {
        if(0 == x)
            return -1;
    
        int c = UINT_BIT-1;
    
        for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
        if(static_cast<UINT>(x << i))
        {
            x <<= i;
            c ^= i;
        }
    
        return c;
    }
    
    0 讨论(0)
  • 2020-11-22 04:17

    c99 has given us log2. This removes the need for all the special sauce log2 implementations you see on this page. You can use the standard's log2 implementation like this:

    const auto n = 13UL;
    const auto Index = (unsigned long)log2(n);
    
    printf("MSB is: %u\n", Index); // Prints 3 (zero offset)
    

    An n of 0UL needs to be guarded against as well, because:

    -∞ is returned and FE_DIVBYZERO is raised

    I have written an example with that check that arbitrarily sets Index to ULONG_MAX here: https://ideone.com/u26vsi


    The visual-studio corollary to ephemient's gcc only answer is:

    const auto n = 13UL;
    unsigned long Index;
    
    _BitScanReverse(&Index, n);
    printf("MSB is: %u\n", Index); // Prints 3 (zero offset)
    

    The documentation for _BitScanReverse states that Index is:

    Loaded with the bit position of the first set bit (1) found

    In practice I've found that if n is 0UL that Index is set to 0UL, just as it would be for an n of 1UL. But the only thing guaranteed in the documentation in the case of an n of 0UL is that the return is:

    0 if no set bits were found

    Thus, similarly to the preferable log2 implementation above the return should be checked setting Index to a flagged value in this case. I've again written an example of using ULONG_MAX for this flag value here: http://rextester.com/GCU61409

    0 讨论(0)
提交回复
热议问题