/** A reasonably efficient prime triangle finder, written for Problem 3-1 of 6.857, Fall 2003. @author Chris Peikert */ import java.math.*; import java.util.*; public class PrimeTriangle { // a big enough window, in which we hope to find $b$ and $c$. A // window of 5000000 is sufficient for $k$=70; for larger values // of $k$ a wider window is probably desired (it does not affect // performance much). public static final int SIEVE_SIZE = 20000000; // the certainty used by isProbablePrime, though for large enough // $k$ the method will override this with a smaller value public static final int CERTAINTY = 30; // an array of the first several prime numbers, used in many places public static final int[] prime = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, }; // a corresponding array of the first prime numbers, as BigIntegers public static final BigInteger[] primeBig = new BigInteger[prime.length]; static { for(int i = 0; i < primeBig.length; i++) { primeBig[i] = BigInteger.valueOf(prime[i]); } } public static void main(String arg[]) { if(arg.length != 1) { System.out.println("usage: PrimeTriangle "); return; } findTriangle(Integer.parseInt(arg[0])); } /** Returns true if the specified number is divisible by a "small" prime, and false otherwise. @param number the number to test */ static boolean smallPrimeDivides(BigInteger number) { for(int i = 0; i < prime.length; i++) { if(number.mod(primeBig[i]).equals(BigInteger.ZERO)) { return true; } } return false; } /** Returns an array containing the values of the given number, mod each prime in the primes array. */ static int[] primeMods(BigInteger number) { int[] out = new int[prime.length]; for(int i = 0; i < out.length; i++) { out[i] = number.mod(primeBig[i]).intValue(); } return out; } /** Attempts to find a prime triangle of $k$ digits, prints the first found, and returns. If no prime triangle is found within the sieve, prints nothing. @param k the desired number of digits */ static void findTriangle(int k) { // the value 10; useful everywhere BigInteger ten = BigInteger.valueOf(10); // the value of the standard multiplier: 10^k BigInteger mult = ten.pow(k); // the value 10^(k-1), used to ensure that both halves of // a number have the right number of digits BigInteger other = ten.pow(k - 1); // Set $a$ = prime having $k$ digits. We prefer this because // $a, b, c$ must be pairwise relatively prime, so it's // easiest just to let $a$ be prime. The funny business with // logs is because the probablePrime method wants the length // of $a$ specified in bits. int numBits = 1 + (int) Math.round(Math.ceil((k - 1) * Math.log(10) / Math.log(2))); // We have no need for cryptographic-strength randomness, so // just use a weak random number generator to pick $a$. BigInteger a = BigInteger.probablePrime(numBits, new Random()); // The value $a || 00...0$, which we'll use a lot BigInteger atop = a.multiply(mult); // Now we want to find a _lot_ of primes whose "top halves" // are $a$, and whose "bottom halves" are $k$-digit numbers. // Therefore we start at $a$ concatenated with 10...0, and // work upwards. BigInteger baseline = atop.add(other); // Next, we use the Sieve of Eratosthenes to quickly rule out // most of the numbers in the range [baseline, baseline + // SIEVE_SIZE). The sieve is slightly optimized: only odd // numbers are represented in it, so we get double the sieve // size from our memory. boolean[] sieve = new boolean[SIEVE_SIZE]; // make it such that: if sieve[i] == true, then (baseline + 2i // + 1) is definitely NOT prime. No need to sieve on the // first prime, because it's 2. for(int j = 1; j < primeBig.length; j++) { // start = smallest index such that (baseline + 2*start + // 1) is divisible by prime[j] int twoTimesStart = (prime[j] - 1 - baseline.mod(primeBig[j]).intValue()); int start; if(twoTimesStart % 2 == 0) { start = twoTimesStart / 2; } else { start = (prime[j] + twoTimesStart) / 2; } // sieve out everything divisible by prime[j] for(int i = start; i < sieve.length; i += prime[j]) { sieve[i] = true; } } System.out.println("Finished sieving; now looking for triangle"); // Now find a bunch of numbers for which $a || b$ and $b || a$ // are prime: in this case, $b$ is called "good." Every time // we find a new good number, check it against all previously // found good numbers. When both concatenations of two good // numbers are also prime, we're done! Vector good = new Vector(); // A vector of all the good elements, times 10^k Vector goodTop = new Vector(); // The values of each good number, mod each small prime Vector goodMods = new Vector(); // And time 10^k Vector goodTopMods = new Vector(); for(int i = 0; i < sieve.length; i++) { // If still a possibility of primality... if(!sieve[i]) { // Recall: $b$ corresponds to $other$, plus the twice // the sieve offset, plus 1 BigInteger b = other.add(BigInteger.valueOf(2*i + 1)); // Construct $b || 00...0$, because it will be used // many times. BigInteger btop = b.multiply(mult); // Test if $a || b$ and $b || a$ are prime. We do the // second test first (and do a fast prime divisibility // test before that), because it's more likely to // return false and short-circuit (recall the first // number, $a || b$, already made it through the // sieve). BigInteger ba = btop.add(a); if(!smallPrimeDivides(ba) && ba.isProbablePrime(CERTAINTY) && atop.add(b).isProbablePrime(CERTAINTY)) { // Give us something to look at System.out.print("."); // $b$ is good; add it for future checks good.addElement(b); goodTop.addElement(btop); // Compute $b$ and $b || 00...0$ mod every small // prime, and save the values int[] bmod = primeMods(b); int[] btopmod = primeMods(btop); goodMods.addElement(bmod); goodTopMods.addElement(btopmod); // Check if $b$ works with any other $c$ in the // good vector, i.e. if $b || c$ and $c || b$ are // both prime. for(int j = 0; j < good.size() - 1; j++) { BigInteger c = (BigInteger) good.elementAt(j); BigInteger ctop = (BigInteger) goodTop.elementAt(j); int[] cmod = (int[]) goodMods.elementAt(j); int[] ctopmod = (int[]) goodTopMods.elementAt(j); // Construct $b || c$ and $c || b$ BigInteger bc = btop.add(c); BigInteger cb = ctop.add(b); // First, do a lot of cheap tests to see if // one of the values is non-prime. Test if $b // || c$ or $c || b$ are divisible by a small // prime, with short-circuiting boolean divisible = false; for(int m = 0; m < prime.length; m++) { if((btopmod[m] + cmod[m]) % prime[m] == 0 || (ctopmod[m] + bmod[m]) % prime[m] == 0) { divisible = true; break; } } if(divisible) { continue; } // if $gcd(b,c)$ != 1, then neither of $b || // c$, $c || b$ is prime. if(!b.gcd(c).equals(BigInteger.ONE)) { continue; } // Only do expensive isProbablePrime tests if // we've passed the cheap tests if(bc.isProbablePrime(CERTAINTY) && cb.isProbablePrime(CERTAINTY)) { System.out.println("\ntriangle = (" + a + "," + b + "," + c + ")"); return; } } } } } } }