Calculating Extremely Large Powers of 2
I have made a program in Java that cal开发者_Python百科culates powers of two, but it seems very inefficient. For smaller powers (2^4000, say), it does it in less than a second. However, I am looking at calculating 2^43112609, which is one greater than the largest known prime number. With over 12 million digits, it will take a very long time to run. Here's my code so far:
import java.io.*;
public class Power
{
private static byte x = 2;
private static int y = 43112609;
private static byte[] a = {x};
private static byte[] b = {1};
private static byte[] product;
private static int size = 2;
private static int prev = 1;
private static int count = 0;
private static int delay = 0;
public static void main(String[] args) throws IOException
{
File f = new File("number.txt");
FileOutputStream output = new FileOutputStream(f);
for (int z = 0; z < y; z++)
{
product = new byte[size];
for (int i = 0; i < a.length; i++)
{
for (int j = 0; j < b.length; j++)
{
product[i+j] += (byte) (a[i] * b[j]);
checkPlaceValue(i + j);
}
}
b = product;
for (int i = product.length - 1; i > product.length - 2; i--)
{
if (product[i] != 0)
{
size++;
if (delay >= 500)
{
delay = 0;
System.out.print(".");
}
delay++;
}
}
}
String str = "";
for (int i = (product[product.length-1] == 0) ?
product.length - 2 : product.length - 1; i >= 0; i--)
{
System.out.print(product[i]);
str += product[i];
}
output.write(str.getBytes());
output.flush();
output.close();
System.out.println();
}
public static void checkPlaceValue(int placeValue)
{
if (product[placeValue] > 9)
{
byte remainder = (byte) (product[placeValue] / 10);
product[placeValue] -= 10 * remainder;
product[placeValue + 1] += remainder;
checkPlaceValue(placeValue + 1);
}
}
}
This isn't for a school project or anything; just for the fun of it. Any help as to how to make this more efficient would be appreciated! Thanks!
Kyle
P.S. I failed to mention that the output should be in base-10, not binary.
The key here is to notice that:
2^2 = 4
2^4 = (2^2)*(2^2)
2^8 = (2^4)*(2^4)
2^16 = (2^8)*(2^8)
2^32 = (2^16)*(2^16)
2^64 = (2^32)*(2^32)
2^128 = (2^64)*(2^64)
... and in total of 25 steps ...
2^33554432 = (2^16777216)*(16777216)
Then since:
2^43112609 = (2^33554432) * (2^9558177)
you can find the remaining (2^9558177)
using the same method, and since (2^9558177 = 2^8388608 * 2^1169569)
, you can find 2^1169569
using the same method, and since (2^1169569 = 2^1048576 * 2^120993)
, you can find 2^120993
using the same method, and so on...
EDIT: previously there was a mistake in this section, now it's fixed:
Also, further simplification and optimization by noticing that:
2^43112609 = 2^(0b10100100011101100010100001)
2^43112609 =
(2^(1*33554432))
* (2^(0*16777216))
* (2^(1*8388608))
* (2^(0*4194304))
* (2^(0*2097152))
* (2^(1*1048576))
* (2^(0*524288))
* (2^(0*262144))
* (2^(0*131072))
* (2^(1*65536))
* (2^(1*32768))
* (2^(1*16384))
* (2^(0*8192))
* (2^(1*4096))
* (2^(1*2048))
* (2^(0*1024))
* (2^(0*512))
* (2^(0*256))
* (2^(1*128))
* (2^(0*64))
* (2^(1*32))
* (2^(0*16))
* (2^(0*8))
* (2^(0*4))
* (2^(0*2))
* (2^(1*1))
Also note that 2^(0*n) = 2^0 = 1
Using this algorithm, you can calculate the table of 2^1
, 2^2
, 2^4
, 2^8
, 2^16
... 2^33554432
in 25 multiplications. Then you can convert 43112609
into its binary representation, and easily find 2^43112609
using less than 25 multiplications. In total, you need to use less than 50 multiplications to find any 2^n
where n
is between 0 and 67108864.
Displaying it in binary is easy and fast - as quickly as you can write to disk! 100000...... :D
Let n = 43112609.
Assumption: You want to print 2^n in decimal.
While filling a bit vector than represents 2^n in binary is trivial, converting that number to decimal notation will take a while. For instance, the implementation of java.math.BigInteger.toString takes O(n^2) operations. And that's probably why
BigInteger.ONE.shiftLeft(43112609).toString()
still hasn't terminated after an hour of execution time ...
Let's start with an asymptotic analysis of your algorithm. Your outer loop will execute n times. For each iteration, you'll do another O(n^2) operations. That is, your algorithm is O(n^3), so poor scalability is expected.
You can reduce this to O(n^2 log n) by making use of
x^64 = x^(2*2*2*2*2*2) = ((((((x^2)^2)^2)^2)^2)^2
(which requires only 8 multiplications) rather than the 64 multiplications of
x^64 = x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x
(Generalizing to arbitrary exponents is left as exercise for you. Hint: Write the exponent as binary number - or look at Lie Ryan's answer).
For speeding up multiplication, you might employ the Karatsuba Algorithm, reducing the overall runtime to O(n^((log 3)/(log 2)) log n).
As mentioned, powers of two correspond to binary digits. Binary is base 2, so each digit is double the value of the previous one.
For example:
1 = 2^0 = b1
2 = 2^1 = b10
4 = 2^2 = b100
8 = 2^3 = b1000
...
Binary is base 2 (that's why it's called "base 2", 2 is the the base of the exponents), so each digit is double the value of the previous one. The shift operator ('<<' in most languages) is used to shift each binary digit to the left, each shift being equivalent to a multiply by two.
For example:
1 << 6 = 2^6 = 64
Being such a simple binary operation, most processors can do this extremely quickly for numbers which can fit in a register (8 - 64 bits, depending on the processor). Doing it with larger numbers requires some type of abstraction (Bignum for example), but it still should be an extremely quick operation. Nevertheless, doing it to 43112609 bits will take a little work.
To give you a little context, 2 << 4311260 (missing the last digit) is 1297181 digits long. Make sure you have enough RAM to handle the output number, if you don't your computer will be swapping to disk, which will cripple your execution speed.
Since the program is so simple, also consider switching to a language which compiles directly into assembly, such as C.
In truth, generating the value is trivial (we already know the answer, a one followed by 43112609 zeros). It will take quite a bit longer to convert it into decimal.
As @John SMith suggests, you can try. 2^4000
System.out.println(new BigInteger("1").shiftLeft(4000));
EDIT: Turning a binary into a decimal is an O(n^2) problem. When you double then number of bits you double the length of each operation and you double the number of digits produced.
2^100,000 takes 0.166 s
2^1000,000 takes 11.7 s
2^10,000,000 should take 1200 seconds.
NOTE: The time taken is entriely in the toString(), not the shiftLeft which takes < 1 ms even for 10 million.
The other key to notice is that your CPU is much faster at multiplying ints and longs than you are by doing long multiplication in Java. Get that number split up into long (64-byte) chunks, and multiply and carry the chunks instead of individual digits. Coupled with the previous answer (using squaring instead of sequential multiplication of 2) will probably speed it up by a factor of 100x or more.
Edit
I attempted to write a chunking and squaring method and it runs slightly slower than BigInteger (13.5 seconds vs 11.5 seconds to calculate 2^524288). After doing some timings and experiments, the fastest method seems to be repeated squaring with the BigInteger class:
public static String pow3(int n) {
BigInteger bigint = new BigInteger("2");
while (n > 1) {
bigint = bigint.pow(2);
n /= 2;
}
return bigint.toString();
}
- Some timing results for power of 2 exponents (2^(2^n) for some n)
- 131072 - 0.83 seconds
- 262144 - 3.02 seconds
- 524288 - 11.75 seconds
- 1048576 - 49.66 seconds
At this rate of growth, it would take approximately 77 hours to calculate 2^33554432, let alone the time storing and adding all the powers together to make the final result of 2^43112609.
Edit 2
Actually, for really large exponents, the BigInteger.ShiftLeft method is the fastest. I estimate that for 2^33554432 with ShiftLeft, it would take approximately 28-30 hours. Wonder how fast a C or Assembly version would take...
Because one actually wants all the digits of the result (unlike, e.g. RSA, where one is only interested in the residue mod a number that's much smaller than the numbers we have here) I think the best approach is probably to extract nine decimal digits at once using long division implemented using multiplication. Start with residue equal zero, and apply the following to each 32 bits in turn (MSB first)
residue = (residue SHL 32)+data result = 0 temp = (residue >> 30) temp += (temp*316718722) >> 32 result += temp; residue -= temp * 1000000000; while (residue >= 1000000000) /* I don't think this loop ever runs more than twice */ { result ++; residue -= 1000000000; }
Then store the result in the 32 bits just read, and loop through each lower word. The residue after the last step will be the nine bottom decimal digits of the result. Since the computation of a power of two in binary will be quick and easy, I think dividing out to convert to decimal may be the best approach.
BTW, this computes 2^640000 in about 15 seconds in vb.net, so 2^43112609 should be about five hours to compute all 12,978,188 digits.
精彩评论