1 /* 2 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 /* 27 * @test 28 * @bug 8026236 29 * @summary test primality verification methods in BigInteger 30 * @author bpb 31 */ 32 import java.io.BufferedReader; 33 import java.io.File; 34 import java.io.FileInputStream; 35 import java.io.FileNotFoundException; 36 import java.io.IOException; 37 import java.io.InputStream; 38 import java.io.InputStreamReader; 39 import java.math.BigInteger; 40 import java.net.URL; 41 import java.util.NavigableSet; 42 import java.util.Random; 43 import java.util.TreeSet; 44 import static java.util.stream.Collectors.toCollection; 45 import java.util.stream.Stream; 46 import java.util.zip.GZIPInputStream; 47 48 public class PrimeTest { 49 50 private static final String DEFAULT_PRIMES_FILE = "primes-100000.txt.gz"; 51 private static final int DEFAULT_MAX_NUM_PRIMES = 1000000; 52 private static final int DEFAULT_CERTAINTY = 100; 53 private static final int NUM_NON_PRIMES = 10000; 54 private static final String DEFAULT_PRIMES_SOURCE; 55 56 // Set up default prime number source 57 static { 58 try { 59 String dir = System.getProperty("test.src", "."); 60 File testFile = new File(dir, DEFAULT_PRIMES_FILE); 61 DEFAULT_PRIMES_SOURCE = testFile.getAbsolutePath(); 62 } catch (Throwable t) { 63 throw new Error(t); 64 } 65 } 66 67 /** 68 * Run the test. 69 * 70 * @param args The parameters. The zeroth parameter should be either a file 71 * path or a URL string. 72 * @throws Throwable 73 */ 74 public static void main(String[] args) throws Throwable { 75 // Prepare arguments 76 String primesSource = args.length > 0 ? args[0] : DEFAULT_PRIMES_SOURCE; 77 int maxNumPrimes = args.length > 1 ? Integer.valueOf(args[1]) : DEFAULT_MAX_NUM_PRIMES; 78 int certainty = args.length > 2 ? Integer.valueOf(args[2]) : DEFAULT_CERTAINTY; 79 boolean parallel = args.length > 3 ? Boolean.valueOf(args[3]) : true; 80 81 // Echo parameter settings 82 System.out.println("Primes source: " + primesSource + "\nupper bound = " + maxNumPrimes 83 + "\ncertainty = " + certainty 84 + "\nparallel = " + parallel); 85 86 // Check whether known primes are identified as such 87 boolean primeTest = checkPrime(primesSource, maxNumPrimes, certainty, parallel); 88 System.out.println("Prime test result: " + (primeTest ? "SUCCESS" : "FAILURE")); 89 if (!primeTest) { 90 System.err.println("Prime test failed"); 91 } 92 93 // Check whether known non-primes are not identified as primes 94 boolean nonPrimeTest = checkNonPrime(primesSource, maxNumPrimes, certainty); 95 System.out.println("Non-prime test result: " + (nonPrimeTest ? "SUCCESS" : "FAILURE")); 96 97 if (!primeTest || !nonPrimeTest) { 98 throw new Exception("PrimeTest FAILED!"); 99 } 100 101 System.out.println("PrimeTest succeeded!"); 102 } 103 104 /** 105 * Parse the primes into a list of [@code BigInteger}s. 106 * 107 * @param gis InputStream of prime numbers assumed to be from a GZIPped text 108 * file with one number per line in the default character set 109 * @param n The maximum number of primes to load 110 * @return A {@code NavigableSet<BigInteger>} containing the loaded primes 111 * @throws IOException If something nasty happens 112 */ 113 private static NavigableSet<BigInteger> parsePrimesStream(InputStream is, long n) throws IOException { 114 GZIPInputStream gis = new GZIPInputStream(is); 115 BufferedReader br = new BufferedReader(new InputStreamReader(gis)); 116 try (Stream<String> lines = br.lines()) { 117 return lines.limit(n).map(BigInteger::new).collect(toCollection(TreeSet::new)); 118 } 119 } 120 121 private static NavigableSet<BigInteger> parsePrimesFile(File f, long n) throws IOException { 122 InputStream gis = new FileInputStream(f); 123 return parsePrimesStream(gis, n); 124 } 125 126 private static NavigableSet<BigInteger> parsePrimesURL(URL u, long n) throws IOException { 127 InputStream is = u.openStream(); 128 return parsePrimesStream(is, n); 129 } 130 131 private static NavigableSet<BigInteger> parsePrimes(String primesSource, long n) throws IOException { 132 File file = new File(primesSource); 133 if (file.exists()) { 134 return parsePrimesFile(file, n); 135 } else { 136 URL url = new URL(primesSource); 137 return parsePrimesURL(url, n); 138 } 139 } 140 141 /** 142 * Verifies whether the fraction of probable primes detected is at least 1 - 143 * 1/2^certainty. 144 * 145 * @return true if and only if the test succeeds 146 */ 147 private static boolean checkPrime(String primesSource, int maxNumPrimes, 148 int certainty, 149 boolean parallel) throws FileNotFoundException, IOException { 150 151 NavigableSet<BigInteger> primes = parsePrimes(primesSource, maxNumPrimes); 152 153 System.out.println(String.format("Loaded %d primes", primes.size())); 154 155 long probablePrimes = (parallel ? primes.parallelStream() : primes.stream()) 156 .filter(bi -> bi.isProbablePrime(certainty)) 157 .count(); 158 159 System.out.println(probablePrimes + " probable primes out of " 160 + primes.size() + " candidates"); 161 if (probablePrimes != primes.size()) { 162 System.err.println("Number of probable primes unequal to number of actual primes"); 163 } 164 165 // N = certainty / 2 166 // Success if p/t >= 1 - 1/4^N 167 // or (p/t)*4^N >= 4^N - 1 168 // or p*4^N >= t*(4^N - 1) 169 BigInteger p = BigInteger.valueOf(probablePrimes); 170 BigInteger t = BigInteger.valueOf(primes.size()); 171 BigInteger fourToTheC = BigInteger.valueOf(4).pow(certainty / 2); 172 BigInteger fourToTheCMinusOne = fourToTheC.subtract(BigInteger.ONE); 173 BigInteger left = p.multiply(fourToTheC); 174 BigInteger right = t.multiply(fourToTheCMinusOne); 175 176 if (left.compareTo(right) < 0) { 177 System.err.println("Probable prime certainty test failed."); 178 } 179 180 return left.compareTo(right) >= 0; 181 } 182 183 /** 184 * Verifies whether all {@code BigInteger}s in the tested range for which 185 * {@code isProbablePrime()} returns {@code false} are <i>not</i> 186 * prime numbers. 187 * 188 * @return true if and only if the test succeeds 189 */ 190 private static boolean checkNonPrime(String primesSource, int maxNumPrimes, 191 int certainty) throws FileNotFoundException, IOException { 192 NavigableSet<BigInteger> primes = parsePrimes(primesSource, maxNumPrimes); 193 194 int maxPrime = 100000; 195 try { 196 maxPrime = primes.last().intValueExact(); 197 } catch (ArithmeticException e) { 198 // ignore it 199 } 200 201 // If it's not a probable prime but is in the primes list then fail. 202 boolean result = !(new Random()).ints(NUM_NON_PRIMES, 2, maxPrime).mapToObj(BigInteger::valueOf).filter(b -> !b.isProbablePrime(certainty)).anyMatch(primes::contains); 203 204 return result; 205 } 206 }