Efficient Exponentiation For HUGE Numbers (I'm

2019-01-27 01:53发布

问题:

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;
}

回答1:

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;
}


回答2:

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)



回答3:

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).