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

后端 未结 27 2619
终归单人心
终归单人心 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:18

    Here are some (simple) benchmarks, of algorithms currently given on this page...

    The algorithms have not been tested over all inputs of unsigned int; so check that first, before blindly using something ;)

    On my machine clz (__builtin_clz) and asm work best. asm seems even faster then clz... but it might be due to the simple benchmark...

    //////// go.c ///////////////////////////////
    // compile with:  gcc go.c -o go -lm
    #include <math.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
    /***************** math ********************/
    
    #define POS_OF_HIGHESTBITmath(a) /* 0th position is the Least-Signif-Bit */    \
      ((unsigned) log2(a))         /* thus: do not use if a <= 0 */  
    
    #define NUM_OF_HIGHESTBITmath(a) ((a)               \
                      ? (1U << POS_OF_HIGHESTBITmath(a))    \
                      : 0)
    
    
    
    /***************** clz ********************/
    
    unsigned NUM_BITS_U = ((sizeof(unsigned) << 3) - 1);
    #define POS_OF_HIGHESTBITclz(a) (NUM_BITS_U - __builtin_clz(a)) /* only works for a != 0 */
    
    #define NUM_OF_HIGHESTBITclz(a) ((a)                    \
                     ? (1U << POS_OF_HIGHESTBITclz(a))  \
                     : 0)
    
    
    /***************** i2f ********************/
    
    double FF;
    #define POS_OF_HIGHESTBITi2f(a) (FF = (double)(ui|1), ((*(1+(unsigned*)&FF))>>20)-1023)
    
    
    #define NUM_OF_HIGHESTBITi2f(a) ((a)                    \
                     ? (1U << POS_OF_HIGHESTBITi2f(a))  \
                     : 0)
    
    
    
    
    /***************** asm ********************/
    
    unsigned OUT;
    #define POS_OF_HIGHESTBITasm(a) (({asm("bsrl %1,%0" : "=r"(OUT) : "r"(a));}), OUT)
    
    #define NUM_OF_HIGHESTBITasm(a) ((a)                    \
                     ? (1U << POS_OF_HIGHESTBITasm(a))  \
                     : 0)
    
    
    
    
    /***************** bitshift1 ********************/
    
    #define NUM_OF_HIGHESTBITbitshift1(a) (({   \
      OUT = a;                  \
      OUT |= (OUT >> 1);                \
      OUT |= (OUT >> 2);                \
      OUT |= (OUT >> 4);                \
      OUT |= (OUT >> 8);                \
      OUT |= (OUT >> 16);               \
          }), (OUT & ~(OUT >> 1)))          \
    
    
    
    /***************** bitshift2 ********************/
    int POS[32] = {0, 1, 28, 2, 29, 14, 24, 3,
                 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19,
                 16, 7, 26, 12, 18, 6, 11, 5, 10, 9};
    
    #define POS_OF_HIGHESTBITbitshift2(a) (({   \
      OUT = a;                  \
      OUT |= OUT >> 1;              \
      OUT |= OUT >> 2;              \
      OUT |= OUT >> 4;              \
      OUT |= OUT >> 8;              \
      OUT |= OUT >> 16;             \
      OUT = (OUT >> 1) + 1;             \
          }), POS[(OUT * 0x077CB531UL) >> 27])
    
    #define NUM_OF_HIGHESTBITbitshift2(a) ((a)              \
                           ? (1U << POS_OF_HIGHESTBITbitshift2(a)) \
                           : 0)
    
    
    
    #define LOOPS 100000000U
    
    int main()
    {
      time_t start, end;
      unsigned ui;
      unsigned n;
    
      /********* Checking the first few unsigned values (you'll need to check all if you want to use an algorithm here) **************/
      printf("math\n");
      for (ui = 0U; ui < 18; ++ui)
        printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITmath(ui));
    
      printf("\n\n");
    
      printf("clz\n");
      for (ui = 0U; ui < 18U; ++ui)
        printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITclz(ui));
    
      printf("\n\n");
    
      printf("i2f\n");
      for (ui = 0U; ui < 18U; ++ui)
        printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITi2f(ui));
    
      printf("\n\n");
    
      printf("asm\n");
      for (ui = 0U; ui < 18U; ++ui) {
        printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITasm(ui));
      }
    
      printf("\n\n");
    
      printf("bitshift1\n");
      for (ui = 0U; ui < 18U; ++ui) {
        printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift1(ui));
      }
    
      printf("\n\n");
    
      printf("bitshift2\n");
      for (ui = 0U; ui < 18U; ++ui) {
        printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift2(ui));
      }
    
      printf("\n\nPlease wait...\n\n");
    
    
      /************************* Simple clock() benchmark ******************/
      start = clock();
      for (ui = 0; ui < LOOPS; ++ui)
        n = NUM_OF_HIGHESTBITmath(ui);
      end = clock();
      printf("math:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);
    
      start = clock();
      for (ui = 0; ui < LOOPS; ++ui)
        n = NUM_OF_HIGHESTBITclz(ui);
      end = clock();
      printf("clz:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);
    
      start = clock();
      for (ui = 0; ui < LOOPS; ++ui)
        n = NUM_OF_HIGHESTBITi2f(ui);
      end = clock();
      printf("i2f:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);
    
      start = clock();
      for (ui = 0; ui < LOOPS; ++ui)
        n = NUM_OF_HIGHESTBITasm(ui);
      end = clock();
      printf("asm:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);
    
      start = clock();
      for (ui = 0; ui < LOOPS; ++ui)
        n = NUM_OF_HIGHESTBITbitshift1(ui);
      end = clock();
      printf("bitshift1:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);
    
      start = clock();
      for (ui = 0; ui < LOOPS; ++ui)
        n = NUM_OF_HIGHESTBITbitshift2(ui);
      end = clock();
      printf("bitshift2\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);
    
      printf("\nThe lower, the better. Take note that a negative exponent is good! ;)\n");
    
      return EXIT_SUCCESS;
    }
    
    0 讨论(0)
  • I assume your question is for an integer (called v below) and not an unsigned integer.

    int v = 612635685; // whatever value you wish
    
    unsigned int get_msb(int v)
    {
        int r = 31;                         // maximum number of iteration until integer has been totally left shifted out, considering that first bit is index 0. Also we could use (sizeof(int)) << 3 - 1 instead of 31 to make it work on any platform.
    
        while (!(v & 0x80000000) && r--) {   // mask of the highest bit
            v <<= 1;                        // multiply integer by 2.
        }
        return r;                           // will even return -1 if no bit was set, allowing error catch
    }
    

    If you want to make it work without taking into account the sign you can add an extra 'v <<= 1;' before the loop (and change r value to 30 accordingly). Please let me know if I forgot anything. I haven't tested it but it should work just fine.

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

    Since 2^N is an integer with only the Nth bit set (1 << N), finding the position (N) of the highest set bit is the integer log base 2 of that integer.

    http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious

    unsigned int v;
    unsigned r = 0;
    
    while (v >>= 1) {
        r++;
    }
    

    This "obvious" algorithm may not be transparent to everyone, but when you realize that the code shifts right by one bit repeatedly until the leftmost bit has been shifted off (note that C treats any non-zero value as true) and returns the number of shifts, it makes perfect sense. It also means that it works even when more than one bit is set — the result is always for the most significant bit.

    If you scroll down on that page, there are faster, more complex variations. However, if you know you're dealing with numbers with a lot of leading zeroes, the naive approach may provide acceptable speed, since bit shifting is rather fast in C, and the simple algorithm doesn't require indexing an array.

    NOTE: When using 64-bit values, be extremely cautious about using extra-clever algorithms; many of them only work correctly for 32-bit values.

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

    Note that what you are trying to do is calculate the integer log2 of an integer,

    #include <stdio.h>
    #include <stdlib.h>
    
    unsigned int
    Log2(unsigned long x)
    {
        unsigned long n = x;
        int bits = sizeof(x)*8;
        int step = 1; int k=0;
        for( step = 1; step < bits; ) {
            n |= (n >> step);
            step *= 2; ++k;
        }
        //printf("%ld %ld\n",x, (x - (n >> 1)) );
        return(x - (n >> 1));
    }
    

    Observe that you can attempt to search more than 1 bit at a time.

    unsigned int
    Log2_a(unsigned long x)
    {
        unsigned long n = x;
        int bits = sizeof(x)*8;
        int step = 1;
        int step2 = 0;
        //observe that you can move 8 bits at a time, and there is a pattern...
        //if( x>1<<step2+8 ) { step2+=8;
            //if( x>1<<step2+8 ) { step2+=8;
                //if( x>1<<step2+8 ) { step2+=8;
                //}
            //}
        //}
        for( step2=0; x>1L<<step2+8; ) {
            step2+=8;
        }
        //printf("step2 %d\n",step2);
        for( step = 0; x>1L<<(step+step2); ) {
            step+=1;
            //printf("step %d\n",step+step2);
        }
        printf("log2(%ld) %d\n",x,step+step2);
        return(step+step2);
    }
    

    This approach uses a binary search

    unsigned int
    Log2_b(unsigned long x)
    {
        unsigned long n = x;
        unsigned int bits = sizeof(x)*8;
        unsigned int hbit = bits-1;
        unsigned int lbit = 0;
        unsigned long guess = bits/2;
        int found = 0;
    
        while ( hbit-lbit>1 ) {
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
            //when value between guess..lbit
            if( (x<=(1L<<guess)) ) {
               //printf("%ld < 1<<%d %ld\n",x,guess,1L<<guess);
                hbit=guess;
                guess=(hbit+lbit)/2;
                //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
            }
            //when value between hbit..guess
            //else
            if( (x>(1L<<guess)) ) {
                //printf("%ld > 1<<%d %ld\n",x,guess,1L<<guess);
                lbit=guess;
                guess=(hbit+lbit)/2;
                //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
            }
        }
        if( (x>(1L<<guess)) ) ++guess;
        printf("log2(x%ld)=r%d\n",x,guess);
        return(guess);
    }
    

    Another binary search method, perhaps more readable,

    unsigned int
    Log2_c(unsigned long x)
    {
        unsigned long v = x;
        unsigned int bits = sizeof(x)*8;
        unsigned int step = bits;
        unsigned int res = 0;
        for( step = bits/2; step>0; )
        {
            //printf("log2(%ld) v %d >> step %d = %ld\n",x,v,step,v>>step);
            while ( v>>step ) {
                v>>=step;
                res+=step;
                //printf("log2(%ld) step %d res %d v>>step %ld\n",x,step,res,v);
            }
            step /= 2;
        }
        if( (x>(1L<<res)) ) ++res;
        printf("log2(x%ld)=r%ld\n",x,res);
        return(res);
    }
    

    And because you will want to test these,

    int main()
    {
        unsigned long int x = 3;
        for( x=2; x<1000000000; x*=2 ) {
            //printf("x %ld, x+1 %ld, log2(x+1) %d\n",x,x+1,Log2(x+1));
            printf("x %ld, x+1 %ld, log2_a(x+1) %d\n",x,x+1,Log2_a(x+1));
            printf("x %ld, x+1 %ld, log2_b(x+1) %d\n",x,x+1,Log2_b(x+1));
            printf("x %ld, x+1 %ld, log2_c(x+1) %d\n",x,x+1,Log2_c(x+1));
        }
        return(0);
    }
    
    0 讨论(0)
  • 2020-11-22 04:22

    GCC has:

     -- Built-in Function: int __builtin_clz (unsigned int x)
         Returns the number of leading 0-bits in X, starting at the most
         significant bit position.  If X is 0, the result is undefined.
    
     -- Built-in Function: int __builtin_clzl (unsigned long)
         Similar to `__builtin_clz', except the argument type is `unsigned
         long'.
    
     -- Built-in Function: int __builtin_clzll (unsigned long long)
         Similar to `__builtin_clz', except the argument type is `unsigned
         long long'.

    I'd expect them to be translated into something reasonably efficient for your current platform, whether it be one of those fancy bit-twiddling algorithms, or a single instruction.


    A useful trick if your input can be zero is __builtin_clz(x | 1): unconditionally setting the low bit without modifying any others makes the output 31 for x=0, without changing the output for any other input.

    To avoid needing to do that, your other option is platform-specific intrinsics like ARM GCC's __clz (no header needed), or x86's _lzcnt_u32 on CPUs that support the lzcnt instruction. (Beware that lzcnt decodes as bsr on older CPUs instead of faulting, which gives 31-lzcnt for non-zero inputs.)

    There's unfortunately no way to portably take advantage of the various CLZ instructions on non-x86 platforms that do define the result for input=0 as 32 or 64 (according to the operand width). x86's lzcnt does that, too, while bsr produces a bit-index that the compiler has to flip unless you use 31-__builtin_clz(x).

    (The "undefined result" is not C Undefined Behavior, just a value that isn't defined. It's actually whatever was in the destination register when the instruction ran. AMD documents this, Intel doesn't, but Intel's CPUs do implement that behaviour. But it's not whatever was previously in the C variable you're assigning to, that's not usually how things work when gcc turns C into asm. See also Why does breaking the "output dependency" of LZCNT matter?)

    0 讨论(0)
  • 2020-11-22 04:22
    unsigned int
    msb32(register unsigned int x)
    {
            x |= (x >> 1);
            x |= (x >> 2);
            x |= (x >> 4);
            x |= (x >> 8);
            x |= (x >> 16);
            return(x & ~(x >> 1));
    }
    

    1 register, 13 instructions. Believe it or not, this is usually faster than the BSR instruction mentioned above, which operates in linear time. This is logarithmic time.

    From http://aggregate.org/MAGIC/#Most%20Significant%201%20Bit

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