Toggle a given range of bits of an unsigned int in C

1.5k views Asked by At

I am trying to replace the following piece of code

// code version 1
unsigned int time_stx = 11; // given range start
unsigned int time_enx = 19; // given range end
unsigned int time     = 0;  // desired output

while(time_stx < time_enx) time |= (1 << time_stx++);

with the following one without a loop

// code version 2
unsigned int time_stx = 11;
unsigned int time_enx = 19;
unsigned int time     = (1 << time_enx) - (1 << time_stx);

It turns out that in code version 1, time = 522240; in code version 2, time = 0; when I use

printf("%u\n", time);

to compare the result. I would like to know why is this happening and if there is any faster way to toggle bits in a given range. My compiler is gcc (Debian 4.9.2-10) 4.9.2.

Edit:

Thank you for your replies. I have made a silly mistake and I feel embarrassing posting my question without further inspecting my codes. I did

unsigned int time_stx = 11;
unsigned int time_enx = 19;

unsigned int time1    = 0;
while(time_stx < time_enx) time1 |= (1 << time_stx++); // version 1

//// what I should, but forgotten to do
// time_stx = 11;
// time_enx = 19;

// where time_stx = time_enx now...
unsigned int time2    = (1 << time_enx) - (1 << time_stx); // version 2

// then obviously
printf("time1 = %u\n", time1); // time1 = 522240
printf("time2 = %u\n", time2); // time2 = 0

I am so sorry for any inconvenience incurred.

Remark: both time_stx and time_enx are generated in the run-time and are not fixed.

As suggested that I made a mistake and the problem is solved now. Thank you!!

1

There are 1 answers

4
Jonathan Leffler On BEST ANSWER

Read Bit twiddling hacks. Even if the answer isn't in there, you'll be better educated on bit twiddling. Also, the original code is simply setting the bits in the range; toggling means turning 1 bits into 0 bits and vice versa (normally achieved using ^ or xor).

As to the code, I converted three variants of the expression into the following C code:

#include <stdio.h>

static void print(unsigned int v)
{
    printf("0x%.8X = %u\n", v, v);
}

static void bit_setter1(void)
{
    unsigned int time_stx = 11; // given range start
    unsigned int time_enx = 19; // given range end
    unsigned int time     = 0;  // desired output

    while (time_stx < time_enx)
        time |= (1 << time_stx++);

    print(time);
}

static void bit_setter2(void)
{
    unsigned int time_stx = 11;
    unsigned int time_enx = 19;
    unsigned int time     = (1 << time_enx) - (1 << time_stx);
    print(time);
}

static void bit_setter3(void)
{
    unsigned int time = 0xFF << 11;
    print(time);
}

int main(void)
{
    bit_setter1();
    bit_setter2();
    bit_setter3();
    return 0;
}

When I look at the assembler for it (GCC 5.1.0 on Mac OS X 10.10.3), I get:

        .globl _main
_main:
LFB5:
LM1:
LVL0:
        subq    $8, %rsp
LCFI0:
LBB28:
LBB29:
LBB30:
LBB31:
LM2:
        movl    $522240, %edx
        movl    $522240, %esi
        leaq    LC0(%rip), %rdi
        xorl    %eax, %eax
        call    _printf
LVL1:
LBE31:
LBE30:
LBE29:
LBE28:
LBB32:
LBB33:
LBB34:
LBB35:
        movl    $522240, %edx
        movl    $522240, %esi
        xorl    %eax, %eax
        leaq    LC0(%rip), %rdi
        call    _printf
LVL2:
LBE35:
LBE34:
LBE33:
LBE32:
LBB36:
LBB37:
LBB38:
LBB39:
        movl    $522240, %edx
        movl    $522240, %esi
        xorl    %eax, %eax
        leaq    LC0(%rip), %rdi
        call    _printf
LVL3:
LBE39:
LBE38:
LBE37:
LBE36:
LM3:
        xorl    %eax, %eax
        addq    $8, %rsp
LCFI1:
        ret

That's an amazingly large collection of labels!

The compiler has fully evaluated all three minimal bit_setterN() functions and inlined them, along with the call to print, into the body of main(). That includes evaluating the expressions to 522240 each time.

Compilers are good at optimization. Write clear code and let them at it, and they will optimize better than you can. Clearly, if the 11 and 19 are not fixed in your code (they're some sort of computed variables which can vary at runtime), then the precomputation isn't as easy (and bit_setter3() is a non-starter). Then the non-loop code will work OK, as will the loop code.

For the record, the output is:

0x0007F800 = 522240
0x0007F800 = 522240
0x0007F800 = 522240

If your Debian compiler is giving you a zero from one of the code fragments, then there's either a difference between what you compiled and what you posted, or there's a bug in the compiler. On the whole, and no disrespect intended, it is more likely that you've made a mistake than that the compiler has a bug in it that shows up in code as simple as this.