1

I have an integer input that is power of 2 (1, 2, 4, 8 etc). I want the function to return bit position without using log(). For example, for inputs above will return {0, 1, 2, 3} respectively This for C#. Plus if this can be done in SQL.

Thanks!

Icerman
  • 1,099
  • 4
  • 14
  • 30

6 Answers6

5

I don't have VS on my Mac to test this out, but did you want something like this?

public static int Foo(int n)
{
    if (n <= 0)
    {
        return -1; // A weird value is better than an infinite loop.
    }
    int i = 0;
    while (n % 2 == 0)
    {
        n /= 2;
        i++;
    }

    return i;
}
Jesus is Lord
  • 14,971
  • 11
  • 66
  • 97
  • Why not `n & 1` and `n >>= 1`? – harold Apr 14 '12 at 14:26
  • I'm aware of bit-operators. But 1) I figured the compiler would be smart enough to use bit-shifting instead of integer division by 2 and 2) I didn't want to provide a confusing answer to those who weren't familiar with it. – Jesus is Lord Apr 14 '12 at 17:53
  • It is smart enough. However, I find this more confusing than explicitly working with bits. It's an algorithm on bits after all so why hide that behind a layer of normal math? – harold Apr 14 '12 at 17:57
  • I see what you're saying, I think. To an experienced programmer, they may wonder why math operators are being used instead of bit operators, but my answer was targeting the OP, specifically, and not experienced programmers (no offense, OP). I figured someone who didn't know how to solve this problem was still somewhat new to programming (as am I, in some aspects) and I know that I was never explicitly taught bit operators in my first programming courses (or ever, actually, and I'll have 2 classes left after this semester). – Jesus is Lord Apr 14 '12 at 18:03
  • Fair enough. IMO they should be taught, it wouldn't take that much extra time and it would stop the newbies from using `(int)Math.Pow(2, someint)` and such. – harold Apr 14 '12 at 18:17
3

The fastest code I found to do this is from the Bit Twiddling Hacks site. Specifically, the lookup based on the DeBruijn sequence. See http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn

I tested a naive method, a switch-based method, and two of the Bit Twiddling Hacks methods: the DeBruijn sequence, and the other that says, "if you know your value is a power of two."

I ran all of these against an array of 32 million powers of two. That is, integers of the form 2^N, where N is in the range 0..30. A value of 2^31 is a negative number, which causes the naive method to go into an infinite loop.

I compiled the code with Visual Studio 2010 in release mode and ran it without the debugger (i.e. Ctrl+F5). On my system, the averages over several dozen runs are:

  • Naive method: 950 ms
  • Switch method: 660 ms
  • Bithack method 1: 1,154 ms
  • DeBruijn: 105 ms

It's clear that the DeBruijn sequence method is much faster than any of the others. The other Bithack method is inferior here because the conversion from C to C# results in some inefficiencies. For example, the C statement int r = (v & b[0]) != 0; ends up requiring an if or a ternary operator (i.e. ?:) in C#.

Here's the code.

class Program
{
    const int Million = 1000 * 1000;
    static readonly int NumValues = 32 * Million;

    static void Main(string[] args)
    {
        // Construct a table of integers.
        // These are random powers of two.
        // That is 2^N, where N is in the range 0..31.
        Console.WriteLine("Constructing table");
        int[] values = new int[NumValues];
        Random rnd = new Random();
        for (int i = 0; i < NumValues; ++i)
        {
            int pow = rnd.Next(31);
            values[i] = 1 << pow;
        }

        // Run each one once to make sure it's JITted
        GetLog2_Bithack(values[0]);
        GetLog2_DeBruijn(values[0]);
        GetLog2_Switch(values[0]);
        GetLog2_Naive(values[0]);

        Stopwatch sw = new Stopwatch();
        Console.Write("GetLog2_Naive ... ");
        sw.Restart();
        for (int i = 0; i < NumValues; ++i)
        {
            GetLog2_Naive(values[i]);
        }
        sw.Stop();
        Console.WriteLine("{0:N0} ms", sw.ElapsedMilliseconds);

        Console.Write("GetLog2_Switch ... ");
        sw.Restart();
        for (int i = 0; i < NumValues; ++i)
        {
            GetLog2_Switch(values[i]);
        }
        sw.Stop();
        Console.WriteLine("{0:N0} ms", sw.ElapsedMilliseconds);

        Console.Write("GetLog2_Bithack ... ");
        sw.Restart();
        for (int i = 0; i < NumValues; ++i)
        {
            GetLog2_Bithack(values[i]);
        }
        Console.WriteLine("{0:N0} ms", sw.ElapsedMilliseconds);

        Console.Write("GetLog2_DeBruijn ... ");
        sw.Restart();
        for (int i = 0; i < NumValues; ++i)
        {
            GetLog2_DeBruijn(values[i]);
        }
        sw.Stop();
        Console.WriteLine("{0:N0} ms", sw.ElapsedMilliseconds);


        Console.ReadLine();
    }

    static int GetLog2_Naive(int v)
    {
        int r = 0;
        while ((v = v >> 1) != 0)
        {
            ++r;
        }
        return r;
    }

    static readonly int[] MultiplyDeBruijnBitPosition2 = new int[32]
    {
        0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 
        31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
    };

    static int GetLog2_DeBruijn(int v)
    {
        return MultiplyDeBruijnBitPosition2[(uint)(v * 0x077CB531U) >> 27];
    }

    static readonly uint[] b = new uint[] { 0xAAAAAAAA, 0xCCCCCCCC, 0xF0F0F0F0, 0xFF00FF00, 0xFFFF0000};

    static int GetLog2_Bithack(int v)
    {
        int r = (v & b[0]) == 0 ? 0 : 1;
        int x = 1 << 4;
        for (int i = 4; i > 0; i--) // unroll for speed...
        {
            if ((v & b[i]) != 0)
                r |= x;
            x >>= 1;
        }
        return r;
    }

    static int GetLog2_Switch(int v)
    {
        switch (v)
        {
            case 0x00000001: return 0;
            case 0x00000002: return 1;
            case 0x00000004: return 2;
            case 0x00000008: return 3;
            case 0x00000010: return 4;
            case 0x00000020: return 5;
            case 0x00000040: return 6;
            case 0x00000080: return 7;
            case 0x00000100: return 8;
            case 0x00000200: return 9;
            case 0x00000400: return 10;
            case 0x00000800: return 11;
            case 0x00001000: return 12;
            case 0x00002000: return 13;
            case 0x00004000: return 14;
            case 0x00008000: return 15;
            case 0x00010000: return 16;
            case 0x00020000: return 17;
            case 0x00040000: return 18;
            case 0x00080000: return 19;
            case 0x00100000: return 20;
            case 0x00200000: return 21;
            case 0x00400000: return 22;
            case 0x00800000: return 23;
            case 0x01000000: return 24;
            case 0x02000000: return 25;
            case 0x04000000: return 26;
            case 0x08000000: return 27;
            case 0x10000000: return 28;
            case 0x20000000: return 29;
            case 0x40000000: return 30;
            case int.MinValue: return 31;
            default:
                return -1;
        }
    }
}

If I optimize the Bithack code by unrolling the loop and using constants instead of array lookups, its time is the same as the time for the switch statement method.

static int GetLog2_Bithack(int v)
{
    int r = ((v & 0xAAAAAAAA) != 0) ? 1 : 0;
    if ((v & 0xFFFF0000) != 0) r |= (1 << 4);
    if ((v & 0xFF00FF00) != 0) r |= (1 << 3);
    if ((v & 0xF0F0F0F0) != 0) r |= (1 << 2);
    if ((v & 0xCCCCCCCC) != 0) r |= (1 << 1);
    return r;
}
Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • Interesting. The De Bruijn algorithm was not one of the ones I timed. I'll add it to my program on Monday and see how it stacks up. It just occurred to me that one could write a struct with the requisite operators to allow it to be used like `r = (v > 0xFFFF) << 4` or `r |= ((v & b[i]) != 0) << i;`. I wonder if doing so would improve performance, but I don't have time now to check it out :-) – phoog Apr 14 '12 at 20:33
  • Great answer with comparison of four methods! – Icerman Apr 16 '12 at 05:27
2

0] if number is zero or negative, return/throw error

1] In your language, find the construct that converts a number to base 2.

2] convert the base-2 value to string

3] return the length of the string minus 1.

kasavbere
  • 5,873
  • 14
  • 49
  • 72
1

Verbose code, but probably the fastest:

if (x < 1)
    throw SomeException();
if (x < 2)
    return 0;
if (x < 4)
    return 1;
if (x < 8)
    return 2;
//.... etc.

This involves no division, nor conversion to-from double. It requires only comparisons, which are very speedy. See Code Complete, 2nd edition, page 633, for a discussion.

If you know that the input will always be a power of two, you might get better performance from a switch block:

switch (input)
{
    case 1:
        return 0;
    case 2:
        return 1;
    case 4:
        return 2;
    case 8:
        return 3;
    //...
    default:
        throw SomeException();
}

I tested the performance on 10 million random ints, and on 10 million randomly-selected powers of two. The results:

  • Bithacks 1: 1360 milliseconds
  • Bithacks 2: 1103 milliseconds
  • If: 1320 milliseconds
  • Bithacks 1 (powers of 2): 1335 milliseconds
  • Bithacks 2 (powers of 2): 1060 milliseconds
  • Bithacks 3 (powers of 2): 1286 milliseconds
  • If (powers of 2): 1026 milliseconds
  • Switch (powers of 2): 896 milliseconds

I increased the number of iterations by ten times, and got these results:

  • Bithacks 1: 13347 milliseconds
  • Bithacks 2: 10370 milliseconds
  • If: 12918 milliseconds
  • Bithacks 1 (powers of 2): 12528 milliseconds
  • Bithacks 2 (powers of 2): 10150 milliseconds
  • Bithacks 3 (powers of 2): 12384 milliseconds
  • If (powers of 2): 9969 milliseconds
  • Switch (powers of 2): 8937 milliseconds

Now I didn't do any profiling to see if I did something stupid in translating the bit hacks to C# code, nor to see how much of the execution time is spent in the function that computes the log. So this is just a back-of-the-envelope kind of calculation, but it does suggest that the if approach is about the same as the bit hacks algorithms, and switch is a bit faster. Additionally, the if and switch approaches are far easier to understand and maintain.

phoog
  • 42,068
  • 6
  • 79
  • 117
  • 1
    No, not the fastest. That requires O(n) operations, where n is the bit that's set. See http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog for code that does it in O(log n) operations. – Jim Mischel Apr 13 '12 at 23:26
  • @JimMischel have you measured? The algorithm you link to requires fewer steps in pseudocode, but involves more calculations for each operation. If N is 32, and the bit-twiddling operations take more than 6 times longer than the comparison, then the if-then approach will be faster. – phoog Apr 13 '12 at 23:33
  • No, I haven't measured recently. I did measure 10 or 12 years ago when I was doing high performance graphics stuff. I suppose CPUs could have changed so much that 32 conditionals with branches, or the overhead of a dictionary lookup will execute faster than four statements containing simple logic. I doubt it, though. – Jim Mischel Apr 13 '12 at 23:52
  • This is better than the first time I saw it;-) For 32 bit integer, not bad. – Icerman Apr 14 '12 at 00:14
  • @JimMischel I've added some rough timings. – phoog Apr 14 '12 at 00:47
  • 1
    @L.B Disgusting, perhaps, but fast, and easy to understand and maintain. – phoog Apr 14 '12 at 00:59
  • No, not especially fast. The bit twiddling hacks version is six times as fast. – Jim Mischel Apr 14 '12 at 19:00
  • @JimMischel How do you figure that? – phoog Apr 14 '12 at 19:08
  • See my answer. The DeBruijn sequence method from the bit twiddling hacks runs in 1/6 the time of the switch statement. – Jim Mischel Apr 14 '12 at 19:49
1

This is a CPU friendly way to do it:

int bitpos=-1;
while(num>0) {
    num = num>>1;
    bitpos++;
}
return bitpos;

For SQL, use CASE. You can do binary search using nested IF....ELSE if performance is a concern. But with just 32 possible values, the overheads of implementing it could be much more than something simple sequential search.

Dojo
  • 5,374
  • 4
  • 49
  • 79
  • That provides incorrect results. If `num` is equal to 0, your code returns 1. It should return 0. – Jim Mischel Apr 14 '12 at 19:51
  • @Jim Mischel - No. If number is zero. It returns -1. If number is 1 it returns 0, If it is 2 it returns 1, you see, it is the bit position. Anyway the point is to show the logic not something that can be pasted into IDE and run as is. – Dojo Apr 15 '12 at 04:44
-1

find bit position in a power-of-two byte (uint8 Flag with only one bit set) coded in C (don't know about C#)

This returns the bit position 1..8 - you may need to decrement the result for indexing 0..7.

int pos(int a){
    int p = (a & 3) | ((a >> 1) & 6) | ((a >> 2) & 5) | ((a >> 3) & 4) | ((a >> 4) & 15) | ((a >> 5) & 2) | ((a >> 6) & 1);
    return p;
}

For info on the "evolution" of the code snippet see my blogpost here http://blog.edutoolbox.de/?p=263

EDIT:

deBruijn style is about 100x faster ...

ABri
  • 610
  • 6
  • 16