Java 计算2的极大幂

Java 计算2的极大幂,java,primes,Java,Primes,我用Java编写了一个程序,可以计算二的幂,但它似乎效率很低。对于较小的功率(比如说2^4000),它在不到一秒钟的时间内完成。然而,我正在计算2^43112609,它比已知的最大素数大一个。由于超过1200万位数,它需要很长时间才能运行。以下是我目前的代码: import java.io.*; public class Power { private static byte x = 2; private static int y = 43112609; private static b

我用Java编写了一个程序,可以计算二的幂,但它似乎效率很低。对于较小的功率(比如说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);
  }
 }  
}
import java.io.*;
公共阶级权力
{
专用静态字节x=2;
私有静态整数y=43112609;
私有静态字节[]a={x};
私有静态字节[]b={1};
私有静态字节[]乘积;
私有静态int size=2;
私有静态int prev=1;
私有静态整数计数=0;
专用静态整数延迟=0;
公共静态void main(字符串[]args)引发IOException
{
文件f=新文件(“number.txt”);
FileOutputStream输出=新的FileOutputStream(f);
对于(intz=0;zproduct.length-2;i--)
{
如果(产品[i]!=0)
{
大小++;
如果(延迟>=500)
{
延迟=0;
系统输出打印(“.”);
}
延迟++;
}
}
}
字符串str=“”;
对于(int i=(乘积[product.length-1]==0)?
product.length-2:product.length-1;i>=0;i--)
{
系统输出打印(产品[i]);
str+=产品[i];
}
output.write(str.getBytes());
output.flush();
output.close();
System.out.println();
}
公共静态无效检查placeValue(int placeValue)
{
if(产品[placeValue]>9)
{
字节余数=(字节)(乘积[placeValue]/10);
产品[placeValue]-=10*余数;
产品[placeValue+1]+=余数;
检查placeValue(placeValue+1);
}
}  
}
这不是为了学校的项目或别的什么;只是为了好玩。任何关于如何提高效率的帮助都将不胜感激!谢谢

凯尔


另外,我没有提到输出应该是base-10格式,而不是二进制格式。

以二进制格式显示它既简单又快速,只要你能写到磁盘上就可以了!100000...... :如前所述,二的幂对应于二进制数字。二进制是以2为基数的,因此每个数字的值都是前一个数字的两倍

例如:

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

二进制是基数2(这就是为什么它被称为“基数2”,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,即使1000万次,也需要<1 ms。

这里的关键是要注意:

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^116956569=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


使用此算法,您可以在25次乘法中计算
2^1
2^2
2^4
2^8
2^16
2^33554432
。然后您可以将
43112609
转换为其二进制表示形式,并使用少于25次的乘法轻松找到
2^43112609
。I总的来说,您需要使用少于50次乘法来查找
2^n
n
介于0和67108864之间的任何
2^n

需要注意的另一个关键点是,与在Java中进行长乘法相比,您的CPU在整数和长乘法方面要快得多。将该数字拆分为长(64字节)将数据块相乘并携带数据块,而不是单个数字。再加上前面的答案(使用平方运算而不是顺序乘法2),可能会将其速度提高100倍或更多

编辑

我尝试编写一个分块和平方方法,它的运行速度略慢于BigInteger(计算2^524288的时间为13.5秒,而计算2^524288的时间为11.5秒)。在进行了一些计时和实验之后,最快的方法似乎是重复使用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或汇编版本需要多快…

让n=43112609

假设:您希望以十进制打印2^n

虽然在二进制中填充表示2^n的位向量很简单,但将该数字转换为十进制表示法需要一段时间。例如,java.math.biginger.toString的实现需要O(n^2)操作。这可能就是原因

BigInteger.ONE.shiftLeft(43112609).toString()
执行一小时后仍然没有终止

让我们从算法的渐近分析开始。您的外循环将执行n次。对于每个迭代,您将执行另一个O(n^2)O 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; }