What's the quickest way to compute log2 of an integer in C#?
Slight improvement to Guffa's answer... Since the amount you are adding to the result is always a power of two using bit operations can produce slight improvement on some architectures. Also since our context is bit patterns it is slightly more readable to use hexadecimal. In this case it is useful to shift the arithmetic by a power of 2.
int bits = 0;
if (n > 0xffff) {
n >>= 16;
bits = 0x10;
}
if (n > 0xff) {
n >>= 8;
bits |= 0x8;
}
if (n > 0xf) {
n >>= 4;
bits |= 0x4;
}
if (n > 0x3) {
n >>= 2;
bits |= 0x2;
}
if (n > 0x1) {
bits |= 0x1;
}
Further a check for n==0 should be added since the above will yield a result of 0 and Log(0) is undefined (regardless of base).
In ARM assembly this algorithm produces very compact code as the branch after comparison can be eliminated with conditional instructions which avoids pipeline flushing. For Example:
if (n > 0xff) {
n >>= 8;
bits |= 0x8;
}
becomes (let R0 = n, R1 = bits)
CMP R0, $0xff
MOVHI R0, R0, LSR $8
ORRHI R1, R1, $0x8
You can simply count how many times you have to remove bits until the value is zero:
int bits = 0;
while (n > 0) {
bits++;
n >>= 1;
}
More efficient for large numbers, you can count groups of bits first:
int bits = 0;
while (n > 255) {
bits += 8;
n >>= 8;
}
while (n > 0) {
bits++;
n >>= 1;
}
Edit:
The most efficient method would be to use the binary steps that Flynn1179 suggested (upvoted for the inspiration :), but expanding the loop into hard coded checks. This is at least twice as fast as the method above, but also more code:
int bits = 0;
if (n > 32767) {
n >>= 16;
bits += 16;
}
if (n > 127) {
n >>= 8;
bits += 8;
}
if (n > 7) {
n >>= 4;
bits += 4;
}
if (n > 1) {
n >>= 2;
bits += 2;
}
if (n > 0) {
bits++;
}
Efficiency in terms of lines of code, or runtime execution speed?
Code's easy: Math.log(n, 2)
.
Runtime speed's a little trickier, but you can do it with a kind of 'binary search':
int bits = 1;
for (int b = 16; b >=1; b/=2)
{
int s = 1 << b;
if (n >= s) { n>>=b; bits+=b; }
}
I'm not 100% certain I've got the logic right there, but hopefully the idea's clear. There might be some overheads in the .NET VM, but in principle it should be faster.
The 16
in the for loop initialializer is based on half the number of bits needed for an int. If you're working with longs, start it at 32, etc.