计算2的极大功率

时间:2010-11-04 22:29:39

标签: java primes

我用Java编写了一个计算2的幂的程序,但它似乎非常低效。对于较小的功率(比如2 ^ 4000),它可以在不到一秒的时间内完成。但是,我正在考虑计算2 ^ 43112609,这比最大的已知素数大一个。超过1200万个数字,运行需要很长时间。到目前为止,这是我的代码:

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

这不适用于学校项目或其他任何事情;就是图个好玩儿。任何有关如何提高效率的帮助将不胜感激!谢谢!

凯尔

P.S。我没有提到输出应该是基数10,而不是二进制。

7 个答案:

答案 0 :(得分:22)

这里的关键是要注意:

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)

然后:

2^43112609 = (2^33554432) * (2^9558177)

您可以使用相同的方法找到剩余的(2^9558177),自(2^9558177 = 2^8388608 * 2^1169569)以来,您可以使用相同的方法找到2^1169569,自(2^1169569 = 2^1048576 * 2^120993)以来,您可以找到2^120993使用相同的方法,等等......

编辑:之前在此部分中存在错误,现在已修复:

此外,通过注意:

进一步简化和优化
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))

另请注意2^(0*n) = 2^0 = 1

使用此算法,您可以计算2^12^22^42^82^16 ... 2^33554432的表格在25次乘法中。然后,您可以将43112609转换为其二进制表示,并使用少于25次乘法轻松找到2^43112609。总的来说,您需要使用少于50次的乘法来查找2^n,其中n介于0和67108864之间。

答案 1 :(得分:19)

以二进制显示它既简单又快捷 - 只需写入磁盘即可! 100000 ......:D

答案 2 :(得分:6)

设n = 43112609。

假设:您想在十进制中打印2 ^ n。

虽然填充比二进制表示2 ^ n的位向量是微不足道的,但将该数字转换为十进制表示法需要一段时间。例如,java.math.BigInteger.toString的实现需要O(n ^ 2)次操作。这可能就是为什么

BigInteger.ONE.shiftLeft(43112609).toString()

在执行一小时后仍然没有终止......

让我们从您的算法的渐近分析开始。你的外循环将执行n次。对于每次迭代,您将执行另一个O(n ^ 2)操作。也就是说,你的算法是O(n ^ 3),因此预期可扩展性很差。

您可以使用

将其减少到O(n ^ 2 log n)

x ^ 64 = x ^(2 * 2 * 2 * 2 * 2 * 2)=((((((x ^ 2)^ 2)^ 2)^ 2)^ 2)^ 2

(仅需要8次乘法)而不是

的64次乘法

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

(对任意指数的推广留给你作为练习。提示:将指数写成二进制数 - 或者看看Lie Ryan的答案。)

为了加速乘法,你可以使用Karatsuba Algorithm,将整个运行时间减少到O(n ^((log 3)/(log 2))log n)。

答案 3 :(得分:5)

如上所述,2的幂对应于二进制数字。二进制是基数2,因此每个数字是前一个数字的两倍。

例如:

    1 = 2^0 = b1
    2 = 2^1 = b10
    4 = 2^2 = b100
    8 = 2^3 = b1000
    ...

Binary是base 2(这就是为什么它被称为“base 2”,2是exponents的基数),所以每个数字是前一个数字的两倍。移位运算符(在大多数语言中为'&lt;&lt;')用于将每个二进制数字向左移位,每个移位等于乘以2。

例如:

1 << 6 = 2^6 = 64

作为一个简单的二进制操作,大多数处理器可以非常快速地为可以放入寄存器的数字(8 - 64位,取决于处理器)执行此操作。使用更大的数字需要某种类型的抽象(例如Bignum),但它仍然应该是一个非常快速的操作。不过,这样做43112609位需要一些工作。

为了给你一点背景,2&lt;&lt; 4311260(缺少最后一位数)是1297181位长。确保您有足够的RAM来处理输出编号,如果您的计算机没有交换到磁盘,这将削弱您的执行速度。

由于程序非常简单,还可以考虑切换到直接编译成汇编的语言,例如C.

事实上,产生价值是微不足道的(我们已经知道了答案,其中一个是43112609零)。将它转换为十进制需要相当长的时间。

答案 4 :(得分:2)

正如@John SMith建议的那样,你可以试试。 2 ^ 4000

    System.out.println(new BigInteger("1").shiftLeft(4000));

编辑:将二进制转换为十进制是一个O(n ^ 2)问题。当你加倍比特数时,你将每个操作的长度加倍,并使你产生的数字加倍。

2^100,000 takes 0.166 s
2^1000,000 takes 11.7 s
2^10,000,000 should take 1200 seconds.

注意:时间是在toString()中,而不是shiftLeft,它取自&lt; 1毫秒甚至1000万。

答案 5 :(得分:0)

要注意的另一个关键是你的CPU在增加整数和长时间方面比在Java中长时间乘法要快得多。将该数字拆分为长(64字节)块,并乘以并携带块而不是单个数字。再加上前面的答案(使用平方而不是2的连续乘法)可能会将其加速100倍或更多。

修改

我试图编写一个分块和平方方法,它的运行速度比BigInteger略慢(13.5秒对11.5秒计算2 ^ 524288)。在做了一些时间和实验后,最快的方法似乎是用BigInteger类重复平方:

    public static String pow3(int n) {
    BigInteger bigint = new BigInteger("2");
    while (n > 1) {
        bigint = bigint.pow(2);
        n /= 2;
    }
    return bigint.toString();
}
  • 2个指数的幂的一些时间结果(对于某些n为2 ^(2 ^ n))
  • 131072 - 0.83秒
  • 262144 - 3.02秒
  • 524288 - 11.75秒
  • 1048576 - 49.66秒

按照这种增长速度,计算2 ^ 33554432需要大约77个小时,更不用说将所有权力存储在一起以便最终得到2 ^ 43112609的时间。

修改2

实际上,对于非常大的指数,BigInteger.ShiftLeft方法是最快的。我估计对于ShiftLeft的2 ^ 33554432,大约需要28-30个小时。想知道C或汇编版本的速度有多快......

答案 6 :(得分:0)

因为一个人实际上想要结果的所有数字(不像,例如RSA,其中一个人只对残差mod感兴趣的数字比我们这里的数字小得多)我认为最好的方法可能是提取九使用乘法实现的长除法一次使用十进制数字。从残余等于零开始,依次对每个32位应用以下内容(MSB优先)

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

然后将结果存储在刚刚读取的32位中,并遍历每个低位字。最后一步之后的残差将是结果的九个底部十进制数字。由于二进制2的幂的计算将是快速和简单的,我认为除以转换为十进制可能是最好的方法。

顺便说一句,这在vb.net中大约15秒内计算2 ^ 640000,所以2 ^ 43112609应该是大约5个小时来计算所有12,978,188位数。