19

Is there any very fast method to find a binary logarithm of an integer number? For example, given a number x=52656145834278593348959013841835216159447547700274555627155488768 such algorithm must find y=log(x,2) which is 215. x is always a power of 2.

The problem seems to be really simple. All what is required is to find the position of the most significant 1 bit. There is a well-known method FloorLog, but it is not very fast especially for the very long multi-words integers.

What is the fastest method?

psihodelia
  • 29,566
  • 35
  • 108
  • 157
  • 7
    u cant do O(1) bcuz u got to read the number in O(n) – Timmy Apr 19 '10 at 15:08
  • 1
    ^ Technically, that's O(log₁₀ n), but I see your point. – Christian Mann Mar 15 '11 at 05:58
  • 1
    For a `multi-word[s?] integer` in binary representation, it would seem _identify most significant (non-zero) word(, and the position of that single 1-bit)_ - O(log n), or O(#words). Now, if the representation was required to not have "leading zeroes" (anybody thinking politicians/superiors/sects?), this would be O(1) - finding a valid representation after subtraction would at least require special attention. – greybeard Oct 22 '16 at 02:45
  • 1
    How is the number represented in memory? – HelloGoodbye Jun 21 '18 at 06:53
  • If x is always a power of 2 so contains only a single 1, and you just want the index of the bit that contains it, this is called a "priority encoder" and you can build it with logic gates to do one encoding per clock cycle. The method is to employ a big OR gate over the upper and lower half of the bits, and then switches to select that half containing the one to pass forward to the next stage, which selections yield the bit index in binary. – Paul Nov 03 '19 at 23:03
  • https://stackoverflow.com/questions/2589096/find-most-significant-bit-left-most-that-is-set-in-a-bit-array – Sergei Krivonos May 29 '21 at 09:52

8 Answers8

8

A quick hack: Most floating-point number representations automatically normalise values, meaning that they effectively perform the loop Christoffer Hammarström mentioned in hardware. So simply converting from an integer to FP and extracting the exponent should do the trick, provided the numbers are within the FP representation's exponent range! (In your case, your integer input requires multiple machine words, so multiple "shifts" will need to be performed in the conversion.)

Community
  • 1
  • 1
j_random_hacker
  • 50,331
  • 10
  • 105
  • 169
5

If the integers are stored in a uint32_t a[], then my obvious solution would be as follows:

  1. Run a linear search over a[] to find the highest-valued non-zero uint32_t value a[i] in a[] (test using uint64_t for that search if your machine has native uint64_t support)

  2. Apply the bit twiddling hacks to find the binary log b of the uint32_t value a[i] you found in step 1.

  3. Evaluate 32*i+b.

ndim
  • 35,870
  • 12
  • 47
  • 57
  • 1
    I don't believe a binary search will work here, since `a[]` is not sorted -- there will be 0-words both before and after the single word containing a single 1-bit. – j_random_hacker Apr 21 '10 at 07:18
  • Uhm. Of course. What was I thinking? Linear search will be the only thing to work there. I have updated the answer accordingly. – ndim Apr 21 '10 at 13:06
  • Note: If the segments of the integer are stored in an array, then the data structure holding the integer (of which the array is a component) *should* already have the base-2 logarithm kept on hand at all times for instant access. That is, a library that always knows the base-2 logarithm of a giant integer is going to be much more efficient at addition, multiplication, etc., than one which has to scan for it. – Todd Lehman Jul 14 '14 at 22:30
2

The answer is implementation or language dependent. Any implementation can store the number of significant bits along with the data, as it is often useful. If it must be calculated, then find the most significant word/limb and the most significant bit in that word.

drawnonward
  • 53,459
  • 16
  • 107
  • 112
2

If you're using fixed-width integers then the other answers already have you pretty-well covered.

If you're using arbitrarily large integers, like int in Python or BigInteger in Java, then you can take advantage of the fact that their variable-size representation uses an underlying array, so the base-2 logarithm can be computed easily and quickly in O(1) time using the length of the underlying array. The base-2 logarithm of a power of 2 is simply one less than the number of bits required to represent the number.

So when n is an integer power of 2:

  • In Python, you can write n.bit_length() - 1 (docs).
  • In Java, you can write n.bitLength() - 1 (docs).
kaya3
  • 47,440
  • 4
  • 68
  • 97
1

The best option on top of my head would be a O(log(logn)) approach, by using binary search. Here is an example for a 64-bit ( <= 2^63 - 1 ) number (in C++):

int log2(int64_t num) {
    int res = 0, pw = 0;    
    for(int i = 32; i > 0; i --) {
        res += i;
        if(((1LL << res) - 1) & num)
            res -= i;
    }
    return res;
}

This algorithm will basically profide me with the highest number res such as (2^res - 1 & num) == 0. Of course, for any number, you can work it out in a similar matter:

int log2_better(int64_t num) {
    var res = 0;
    for(i = 32; i > 0; i >>= 1) {
        if( (1LL << (res + i)) <= num )
            res += i;
    }
    return res;
}

Note that this method relies on the fact that the "bitshift" operation is more or less O(1). If this is not the case, you would have to precompute either all the powers of 2, or the numbers of form 2^2^i (2^1, 2^2, 2^4, 2^8, etc.) and do some multiplications(which in this case aren't O(1)) anymore.

lbicsi
  • 63
  • 1
  • 9
1

You can create an array of logarithms beforehand. This will find logarithmic values up to log(N):

#define N 100000
int naj[N];

naj[2] = 1;
for ( int i = 3; i <= N; i++ )
{
    naj[i] = naj[i-1];
    if ( (1 << (naj[i]+1)) <= i )
        naj[i]++;

}

The array naj is your logarithmic values. Where naj[k] = log(k). Log is based on two.

Turkhan Badalov
  • 845
  • 1
  • 11
  • 17
  • How does this allow to find the `binary logarithm of [a multi-words] power of 2` fast? – greybeard Oct 22 '16 at 02:44
  • @greybeard, it is for small numbers to not compute each time logarithm calling log2 from cmath, because it is expensive. This tecnique is often used when creating Sparse tables. – Turkhan Badalov Oct 22 '16 at 03:52
1

This uses binary search for finding the closest power of 2.

public static int binLog(int x,boolean shouldRoundResult){
    // assuming 32-bit integer
    int lo=0;
    int hi=31;
    int rangeDelta=hi-lo;
    int expGuess=0;
    int guess;
    while(rangeDelta>1){
        expGuess=(lo+hi)/2; // or (loGuess+hiGuess)>>1
        guess=1<<expGuess;
        if(guess<x){
            lo=expGuess;
        } else if(guess>x){
            hi=expGuess;            
        } else {
            lo=hi=expGuess;
        }
        rangeDelta=hi-lo;
    }
    if(shouldRoundResult && hi>lo){
        int loGuess=1<<lo;
        int hiGuess=1<<hi;
        int loDelta=Math.abs(x-loGuess);
        int hiDelta=Math.abs(hiGuess-x);
        if(loDelta<hiDelta)
            expGuess=lo;
        else
            expGuess=hi;
    } else {
        expGuess=lo;
    }
    int result=expGuess;
    return result;
}
RollerSimmer
  • 119
  • 4
0

The example in the OP is an integer string of 65 characters, which is not representable by a INT64 or even INT128. It is still very easy to get the Log(2,x) from this string by converting it to a double-precision number. This at least gives you easy access to integers upto 2^1023.

Below you find some form of pseudocode

# 1. read the string
string="52656145834278593348959013841835216159447547700274555627155488768"
# 2. extract the length of the string
l=length(string)                         # l = 65
# 3. read the first min(l,17) digits in a float
float=to_float(string(1: min(17,l) ))
# 4. multiply with the correct power of 10
float = float * 10^(l-min(17,l) )               # float = 5.2656145834278593E64
# 5. Take the log2 of this number and round to the nearest integer
log2 = Round( Log(float,2) )             # 215

Note:

  1. some computer languages can convert arbitrary strings into a double precision number. So steps 2,3 and 4 could be replaced by x=to_float(string)
  2. Step 5 could be done quicker by just reading the double-precision exponent (bits 53 up to and including 63) and subtracting 1023 from it.

Quick example code: If you have awk you can quickly test this algorithm.

The following code creates the first 300 powers of two:

awk 'BEGIN{for(n=0;n<300; n++) print 2^n}'

The following reads the input and does the above algorithm:

awk '{ l=length($0); m = (l > 17 ? 17 : l)
       x = substr($0,1,m) * 10^(l-m)
       print log(x)/log(2)
     }'

So the following bash-command is a convoluted way to create a consecutive list of numbers from 0 to 299:

$ awk 'BEGIN{for(n=0;n<300; n++) print 2^n}' | awk '{ l=length($0); m = (l > 17 ? 17 : l); x = substr($0,1,m) * 10^(l-m); print log(x)/log(2) }'
0
1
2
...
299
kvantour
  • 25,269
  • 4
  • 47
  • 72