Updated with newer answer and better test
Let\'s say I have the number 382 which is 101111110.
How could I randomly turn a bit which is not 0 to
Here is a version based on an algorithm from Bit Twiddling Hacks to select the nth set bit of an integer. For this case, we simply select n at random.
The code has been ported to C#, made to work directly on 32-bit signed integers, and count from the right instead of the left. Furthermore, the optimization to remove all branches has not been preserved here as it yielded slower code on my machine (Intel Core 2 Quad Q9450).
The description on the Bit Twiddling Hacks page does not give much insight into how the algorithm works. I have taken the time to step through and reverse engineer it and what I found is described in detail in the comments below.
In my tests, this algorithm performs very similarly to Kyteland's excellent flipRandomBit over input that is distributed randomly across the full range of 32-bit integers. However, flipRandomBit is slightly faster for numbers with significantly fewer set bits than cleared bits. Conversely, this algorithm is slightly faster for numbers with significantly more set bits than cleared bits.
The OP's benchmark consists entirely of small positive integers, which do not stress flipRandomBit's worst case. If this is an indication of the expected input, then all the more reason to prefer flipRandomBit.
static int ClearRandomSetBit(int input) {
///////////////////////////////////////////////////////////////////////
// ** Step 1 **
// Count the set bits
////////////////////////////////////////////////////////////////////////
// magic numbers
const int m2 = 0x55555555; // 1 zero, 1 one, ...
const int m4 = 0x33333333; // 2 zeros, 2 ones, ...
const int m8 = 0x0f0f0f0f; // 4 zeros, 4 ones, ...
// sequence of 2-bit values representing the counts of each 2 bits.
int c2 = input - ((input >> 1) & m2);
// sequence of 4-bit values representing the counts of each 4 bits.
int c4 = (c2 & m4) + ((c2 >> 2) & m4);
// sequence of 8-bit values representing the counts of each 8 bits.
int c8 = (c4 + (c4 >> 4)) & m8;
// count set bits in input.
int bitCount = (c8 * 0x1010101) >> 24;
///////////////////////////////////////////////////////////////////////////////////
// ** Step 2 **
// Select a random set bit to clear and find it using binary search with our
// knowledge of the bit counts in the various regions.
///////////////////////////////////////////////////////////////////////////////////
// count 16 right-most bits where we'll begin our search
int count = (c8 + (c8 >> 8)) & 0xff;
// position of target bit among the set bits
int target = random.Next(bitCount);
// distance in set bits from the current position to the target
int distance = target + 1;
// current bit position
int pos = 0;
// if the target is not in the right-most 16 bits, move past them
if (distance > count) { pos += 16; distance -= count; }
// if the target is not in the next 8 bits, move past them
count = (c8 >> pos) & 0xff;
if (distance > count) { pos += 8; distance -= count; }
// if the target is not in the next 4 bits, move past them
count = (c4 >> pos) & 0xf;
if (distance > count) { pos += 4; distance -= count; }
// if the target is not in the next 2 bits, move past them
count = (c2 >> pos) & 0x3;
if (distance > count) { pos += 2; distance -= count; }
// if the bit is not the next bit, move past it.
//
// Note that distance and count must be single bits by now.
// As such, distance is greater than count if and only if
// distance equals 1 and count equals 0. This obversation
// allows us to optimize away the final branch.
Debug.Assert((distance & 0x1) == distance);
Debug.Assert((count & 0x1) == count);
count = (input >> pos) & 0x1;
pos += (distance & (count ^ 1));
Debug.Assert((input & (1 << pos)) != 0);
return input ^ (1 << pos);
}