3

I am in the midst of solving a simple combination problem whose solution is 2^(n-1).

The only problem is 1 <= n <= 2^31 -1 (max value for signed 32 bit integer)

I tried using Java's BigInteger class but It times out for numbers 2^31/10^4 and greater, so that clearly doesn't work out.

Furthermore, I am limited to using only built-in classes for Java or C++.

Knowing I require speed, I chose to build a class in C++ which does arithmetic on strings.

Now, when I do multiplication, my program multiplies similarly to how we multiply on paper for efficiency (as opposed to repeatedly adding the strings).

But even with that in place, I can't multiply 2 by itself 2^31 - 1 times, it is just not efficient enough.

So I started reading texts on the problem and I came to the solution of...

2^n = 2^(n/2) * 2^(n/2) * 2^(n%2) (where / denotes integer division and % denotes modulus)

This means I can solve exponentiation in a logarithmic number of multiplications. But to me, I can't get around how to apply this method to my code? How do I choose a lower bound and what is the most efficient way to keep track of the various numbers that I need for my final multiplication?

If anyone has any knowledge on how to solve this problem, please elaborate (example code is appreciated).

UPDATE

Thanks to everyone for all your help! Clearly this problem is meant to be solved in a realistic way, but I did manage to outperform java.math.BigInteger with a power function that only performs ceil(log2(n)) iterations.

If anyone is interested in the code I've produced, here it is...

using namespace std;

bool m_greater_or_equal (string & a, string & b){ //is a greater than or equal to b?
    if (a.length()!=b.length()){
        return a.length()>b.length();
    }
    for (int i = 0;i<a.length();i++){
        if (a[i]!=b[i]){
            return a[i]>b[i];
        }
    }
    return true;
}

string add (string& a, string& b){
    if (!m_greater_or_equal(a,b)) return add(b,a);
    string x = string(a.rbegin(),a.rend());
    string y = string(b.rbegin(),b.rend());
    string result = "";
for (int i = 0;i<x.length()-y.length()+1;i++){
    y.push_back('0');
}

int carry = 0;
for (int i =0;i<x.length();i++){
    char c = x[i]+y[i]+carry-'0'-'0';
    carry = c/10;
    c%=10;
    result.push_back(c+'0');
}
if (carry==1) result.push_back('1');
return string(result.rbegin(),result.rend());

}

string multiply (string&a, string&b){
    string row = b, tmp;
    string result = "0";

    for (int i = a.length()-1;i>=0;i--){

        for (int j= 0;j<(a[i]-'0');j++){
            tmp = add(result,row);
            result = tmp;
        }
        row.push_back('0');
    }
    return result;
}

int counter = 0;

string m_pow (string&a, int exp){
    counter++;
    if(exp==1){
        return a;
    }
    if (exp==0){
        return "1";
    }
    string p = m_pow(a,exp/2);
    string res;
    if (exp%2==0){
        res = "1";  //a^exp%2 is a^0 = 1
    } else {
        res = a;   //a^exp%2 is a^1 = a
    }
    string x = multiply(p,p);
    return multiply(x,res);
    //return multiply(multiply(p,p),res); Doesn't work because multiply(p,p) is not const

}

int main(){


    string x ="2";

    cout<<m_pow(x,5000)<<endl<<endl;
    cout<<counter<<endl;

    return 0;
}
Jimmy Huch
  • 4,400
  • 7
  • 29
  • 34
  • 4
    There are no "built-in" big-number classes in C++. Do you still want to consider popular libraries, or do you want to scrap C++ from the question? I don't think anyone will guide you through creating your own bignum library here when excellent libraries already exist (GMP). – Kerrek SB Jan 07 '12 at 17:49
  • 1
    "Knowing I require speed, I chose to build a class in C++ which does arithmetic on strings." Assuming you mean the same think I do when talking about strings (that is something build of chars) that really won't help your performance much (generally take the biggest possible type as a base). Besides do I understand you correctly that you need numbers in the order of 2^(2^31)? In that case you really require all the speed you can get, so I'd advise against writing such a class yourself. – Grizzly Jan 07 '12 at 17:57
  • If you want the answer in binary, then you don't have to do any computation (just write 1 followed by `n - 1` zeros - as mentioned in Oli's answer). But if you want to print the answer in decimal... well... Don't count on Java's BigInteger to do it... And if you're talking sizes significantly larger than `n > 2^32`, don't count on GMP to do it either... You'll run out of memory before that happens... – Mysticial Jan 07 '12 at 17:58
  • Yeh, I just didn't think of bit arithmetic, that would probably be the best way to go, although the recursive approach that dasblinkenlight brings to the table is also quite interesting and I will try it out as well. – Jimmy Huch Jan 07 '12 at 18:00
  • Just a note on using std::string: if you'd use std::vector for the representation (with an extra bit to indicate the sign) and made sure to use unsigned long long for computations you should be able to improve performance further while essentially using the same approach. – Dietmar Kühl Jan 07 '12 at 20:05
  • @Dietmar I doubt that would improve performance as I only keep 1 digit at each index. If I kept 1 digit numbers in unsigned long, that would be a memory waste because sizeof(char) = 1 while sizeof(long) = 8 – Jimmy Huch Jan 07 '12 at 20:19
  • @JimmyHuch: well, right. However, if you'd work with hands with more finger, e.g. with 1000000000 fingers, your approach stays valid and you can still format the number reasonably simple. – Dietmar Kühl Jan 07 '12 at 20:25
  • @JimmyHuch You should investigate your algorithms, `2^20 = 1048576 ? 1040576` is the first power of 2 your code returns wrong results for, but from then on, it's wrong at least to 32 (didn't check further). – Daniel Fischer Jan 07 '12 at 22:45

3 Answers3

6

As mentioned by @Oli's answer, this is not a question of computing 2^n as that's trivially just a 1 followed by 0s in binary.

But since you want to print them out in decimal, this becomes a question of how to convert from binary to decimal for very large numbers.

My answer to that is that it's not realistic. (I hope this question just stems from curiosity.)

You mention trying to compute 2^(2^31 - 1) and printing that out in decimal. That number is 646,456,993 digits long.

  • Java BigInteger can't do it. It's meant for small numbers and uses O(n^2) algorithms.
  • As mentioned in the comments, there are no built-in BigNum libraries in C++.
  • Even Mathematica can't handle it: General::ovfl : Overflow occurred in computation.
  • Your best bet is to use the GMP library.

If you're just interested in seeing part of the answer:

2^(2^31 - 1) = 2^2147483647 = 

880806525841981676603746574895920 ... 7925005662562914027527972323328

(total: 646,456,993 digits)

This was done using a close-sourced library and took roughly 37 seconds and 3.2 GB of memory on a Core i7 2600K @ 4.4 GHz including the time needed to write all 646 million digits to a massive text file.
(It took notepad longer to open the file than needed to compute it.)


Now to answer your question of how to actually compute such a power in the general case, @dasblinkenlight has the answer to that which is a variant of Exponentiation by Squaring.

Converting from binary to decimal for large numbers is a much harder task. The standard algorithm here is Divide-and-Conquer conversion.

I do not recommend you try to implement the latter - as it's far beyond the scope of starting programmers. (and is also somewhat math-intensive)

Mysticial
  • 464,885
  • 45
  • 335
  • 332
4

You don't need to do any multiplication at all. 2^(n-1) is just 1 << (n-1), i.e. 1 followed by (n-1) zeros (in binary).

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
3

The easiest way to apply this method in your code is to apply it the most direct way - recursively. It works for any number a, not only for 2, so I wrote code that takes a as a parameter to make it more interesting:

MyBigInt pow(MyBigInt a, int p) {
    if (!p) return MyBigInt.One;
    MyBigInt halfPower = pow(a, p/2);
    MyBigInt res = (p%2 == 0) ? MyBigInt.One : a;
    return res * halfPower * halfPower;
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523