What's the best way to toggle the MSB?

佐手、 提交于 2019-12-01 21:38:12
jleahy

The cheat is to pawn it off to the compiler: There are instructions in most CPUs for doing work like this.

The following should do what you want.

i ^ (1 << (sizeof i * CHAR_BIT - clz(i) - 1))

This will translate into the CLZ instruction, which counts the leading zeros.

For GCC, see: http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Other-Builtins.html

One thing to be careful of is that this results in undefined behavior if i == 0.

You should replace clz() with the correct intrinsic for your compiler, In GCC this is __builtin_clz; in Visual Studio C++ this is _BitScanForward.

Eldar Abusalimov

@jleahy has already posted a good option in case of using GCC, I would only leave here a generic implementation of clz which does not use any compiler intrinsics. However, it is not the optimal choice for CPUs which already have native instructions for counting bits (such as x86).

#define __bit_msb_mask(n) (~(~0x0ul >> (n)))   /* n leftmost bits. */

/* Count leading zeroes. */
int clz(unsigned long x) {
    int nr = 0;
    int sh;

    assert(x);

    /* Hope that compiler optimizes out the sizeof check. */
    if (sizeof(x) == 8) {
        /* Suppress "shift count >= width of type" error in case
         * when sizeof(x) is NOT 8, i.e. when it is a dead code anyway. */
        sh = !(x & __bit_msb_mask(sizeof(x)*8/2)) << 5;
        nr += sh; x <<= sh;
    }

    sh = !(x & __bit_msb_mask(1 << 4)) << 4; nr += sh; x <<= sh;
    sh = !(x & __bit_msb_mask(1 << 3)) << 3; nr += sh; x <<= sh;
    sh = !(x & __bit_msb_mask(1 << 2)) << 2; nr += sh; x <<= sh;
    sh = !(x & __bit_msb_mask(1 << 1)) << 1; nr += sh; x <<= sh;
    sh = !(x & __bit_msb_mask(1 << 0)) << 0; nr += sh;

    return nr;
}

Using this function one can toggle the most significant set bit (assuming there is such one) as follows:

x ^= 1ul << (sizeof(x)*8 - clz(x))

Here's an approach using a lookup table, assuming CHAR_BIT == 8:

uint32_t toggle_msb(uint32_t n)
{
    static unsigned char const lookup[] =
                         { 1, 0, 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7 };

    for (unsigned int i = 0; i != sizeof n; ++i)
    {
        // omit the last bit for big-endian machines: ---VVVVVVVVVVVVVVVVVV
        unsigned char * p
                 = reinterpret_cast<unsigned char *>(&n) + sizeof n - i - 1;

        if (*p / 16 != 0) { *p = *p % 16 + (lookup[*p / 16] * 16); return n; }
        if (*p % 16 != 0) { *p = 16 * (*p / 16) + lookup[*p % 16]; return n; }
    }

    return 1;
}

And to just put it all together in some sample code for GCC:

#include <stdio.h>

#define clz(x)  __builtin_clz(x)

int main()
{
    int i = 411;    /* 110011011 */

    if( i != 0 )
        i ^= (1 << (sizeof(i)*8 - clz(i)-1));

    /* i is now 10011011 */
    printf("i = %d\n", i);
    return(0);
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!