Here we shall discuss some high level Bit hacks that if used cleverly can really speed up your program execution time.
We discussed some of the basic hacks in this post:- Low level bit hacks you must know. Please go through the post to get a little understanding of how the things work. Smart learners are anyways welcome to proceed. ;-)
BIT HACK 6: Turn off the rightmost 1-bit.
y = x & (x-1)
Now it finally gets more interesting!!! Bit hacks #1 – #5 were kind of boring to be honest.
This bit hack turns off the rightmost one-bit. For example, given an integer 00101010 (the rightmost 1-bit in bold) it turns it into 00101000. Or given 00010000 it turns it into 0, as there is just a single 1-bit.
Here are more examples:
01010111 (x) & 01010110 (x-1) -------- 01010110 01011000 (x) & 01010111 (x-1) -------- 01010000 10000000 (x = -128) & 01111111 (x-1 = 127 (with overflow)) -------- 00000000 11111111 (x = all bits 1) & 11111110 (x-1) -------- 11111110 00000000 (x = no rightmost 1-bits) & 11111111 (x-1) -------- 00000000
Why does it work?
If you look at the examples and think for a while, you’ll realize that there are two possible scenarios:
1. The value has the rightmost 1 bit. In this case subtracting one from it sets all the lower bits to one and changes that rightmost bit to 0 (so that if you add one now, you get the original value back). This step has masked out the rightmost 1-bit and now AND-ing it with the original value zeroes that rightmost 1-bit out.
2. The value has no rightmost 1 bit (all 0). In this case subtracting one underflows the value (as it’s signed) and sets all bits to 1. AND-ing all zeroes with all ones produces 0.
BIT HACK 7: Isolate the rightmost 1-bit.
y = x & (-x)
This bit hack finds the rightmost 1-bit and sets all the other bits to 0. The end result has only that one rightmost 1-bit set. For example, 01010100 (rightmost bit in bold) gets turned into 00000100.
Here are some more examples:
10111100 (x) & 01000100 (-x) -------- 00000100 01110000 (x) & 10010000 (-x) -------- 00010000 00000001 (x) & 11111111 (-x) -------- 00000001 10000000 (x = -128) & 10000000 (-x = -128) -------- 10000000 11111111 (x = all bits one) & 00000001 (-x) -------- 00000001 00000000 (x = all bits 0, no rightmost 1-bit) & 00000000 (-x) -------- 00000000
This bit hack works because of two’s complement. In two’s complement system -x is the same as ~x+1. Now let’s examine the two possible cases:
1. There is a rightmost 1-bit bi. In this case let’s pivot on this bit and divide all other bits into two flanks – bits to the right and bits to the left. Remember that all the bits to the right bi-1, bi-2 … b0 are 0’s (because bi was the rightmost 1-bit). And bits to the left are the way they are. Let’s call them bi+1, …, bn.
Now, when we calculate -x, we first do ~x which turns bit bi into 0, bits bi-1 … b0 into 1s, and inverts bits bi+1, …, bn, and then we add 1 to this result.
Since bits bi-1 … b0 are all 1’s, adding one makes them carry this one all the way to bit bi, which is the first zero bit.
If we put it all together, the result of calculating -x is that bits bi+1, …, bn get inverted, bit bi stays the same, and bits bi-1, …, b0 are all 0’s.
Now, AND-ing x with -x makes bits bi+1, …, bn all 0, leaves bit bi as is, and sets bits bi-1, …, b0 to 0. Only one bit is left, it’s the bit bi – the rightmost 1-bit.
2. There is no rightmost 1-bit. The value is 0. The negative of 0 in two’s complement is also 0. 0&0 = 0. No bits get turned on.
We have proved rigorously that this bithack is correct.
BIT HACK 8: Right propagate the rightmost 1-bit.
y = x | (x-1)
This is best understood by an example. Given a value 01010000 it turns it into 01011111. All the 0-bits right to the rightmost 1-bit got turned into ones.
This is not a clean hack, though, as it produces all 1’s if x = 0.
Let’s look at more examples:
10111100 (x) | 10111011 (x-1) -------- 10111111 01110111 (x) | 01110110 (x-1) -------- 01110111 00000001 (x) | 00000000 (x-1) -------- 00000001 10000000 (x = -128) | 01111111 (x-1 = 127) -------- 11111111 11111111 (x = -1) | 11111110 (x-1 = -2) -------- 11111111 00000000 (x) | 11111111 (x-1) -------- 11111111
Let’s prove it, though not as rigorously as in the previous bithack (as it’s too time consuming and this is not a scientific publication). There are two cases again. Let’s start with easiest first.
1. There is no rightmost 1-bit. In that case x = 0 and x-1 is -1. -1 in two’s complement is 11111111. OR-ing 0 with 11111111 produces the same 11111111. (Not the desired result, but that’s the way it is.)
2. There is the rightmost 1-bit bi. Let’s divide all the bits in two groups again (like in the previous example). Calculating x-1 modifies only bits to the right, turning bi into 0, and all the lower bits to 1’s. Now OR-ing x with x-1 leaves all the higher bits (to the left) the same, leaves bit bi as it was 1, and since lower bits are all low 1’s it also turns them on. The result is that the rightmost 1-bit got propagated to lower order bits.
BIT HACK 9: Isolate the rightmost 0-bit.
y = ~x & (x+1)
This bithack does the opposite of #7. It finds the rightmost 0-bit, turns off all bits, and sets this bit to 1 in the result. For example, it finds the zero in bold in this number 10101011, producing 00000100.
More examples:
10111100 (x) -------- 01000011 (~x) & 10111101 (x+1) -------- 00000001 01110111 (x) -------- 10001000 (~x) & 01111000 (x+1) -------- 00001000 00000001 (x) -------- 11111110 (~x) & 00000010 (x+1) -------- 00000010 10000000 (x = -128) -------- 01111111 (~x) & 10000001 (x+1) -------- 00000001 11111111 (x = no rightmost 0-bit) -------- 00000000 (~x) & 00000000 (x+1) -------- 00000000 00000000 (x) -------- 11111111 (~x) & 00000001 (x+1) -------- 00000001
Proof: Suppose there is a rightmost 0-bit. Then ~x turns this rightmost 0 bit into 1 bit. And so does x+1 (because bits more right to the rightmost 0 bit are 1’s). Now AND-ing ~x with x+1 evaporates all the bits up to this rightmost 0 bit. This is the highest order bit set in the result. Now what about lower order bits to the right of rightmost 0 bit? They also got evaporated because because x+1 turned them into 0’s (they were 1’s) and ~x turned them into 0’s. They got AND-ed with 0 and evaporated.
BIT HACK 10: Turn on the rightmost 0-bit.
y = x | (x+1)
This hack changes the rightmost 0-bit into 1. For example, given an integer 10100011 it turns it into 10100111.
More examples:
10111100 (x) | 10111101 (x+1) -------- 10111101 01110111 (x) | 01111000 (x+1) -------- 01111111 00000001 (x) | 00000010 (x+1) -------- 00000011 10000000 (x = -128) | 10000001 (x+1) -------- 10000001 11111111 (x = no rightmost 0-bit) | 00000000 (x+1) -------- 11111111 00000000 (x) | 00000001 (x+1) -------- 00000001
Here is the proof as a bunch of true statements. OR-ing x with x+1 does not lose any information. Adding 1 to x fills the first rightmost 0. The result is max{x, x+1}. If x+1 overflows it’s x and there were no 0 bits. If it doesn’t, it’s x+1 which just got rightmost bit filled with 1.