Mega Code Archive

 
Categories / Delphi / Algorithm Math
 

How to determine if a number is prime, quickly (2)

Title: How to determine if a number is prime, quickly (2) *** HTPrimes ************************************************************** version: 1.1, 13 June 2002 (Version history at end of document.) author: john.a.stockton@btopenworld.com Comments, suggestions, bug reports -- all welcome. purpose: Suite of functions to assist exploring the prime integers to High(Cardinal), ie. up to 4,294,967,295. Includes an efficient primality test combining factorisation and SPRP approaches, implemented (almost) entirely in Pascal. licence: Free for non-commercial use. A postcard would be very much appreciated from commercial users (royalty cheques also gratefully received - if your conscience gets the better of you). *************************************************************************** NOTE: The StrongProbablePrime and WeakProbablePrime functions depend on an ancillary function, MulMod. MulMod is a kludge that uses an assembler shortcut. It might not work on all compilers. It has been tested on Delphi 5 (dcc32 v13.0) and Delphi 6 (dcc32 v14.0). The current implementation of MulMod is also prone to trigger #DE exceptions (expressed as EIntOverflow by Delphi), for large values of B in calls to StrongProbablePrime and WeakProbablePrime. A slower, but safe version of the function is included in a comment. *************************************************************************** } unit HTPrimes; interface { *************************************************** Interface *** } const MaxPrime = High(Cardinal) - 4; { The highest prime in the range of the Cardinal type. P(203,280,221) = 4,294,967,291. } type TPrimeFactor = record p, q: Cardinal; end; TPrimeFactorArray = array of TPrimeFactor; { Record and dynamic array types used by the GetPrimeFactors function (see below). } TPrimeArray = array of Cardinal; { Dynamic array type used by the GetGoldbachPairs function (see below). } { ************************************************* Function Declarations *** } function IsPrime(Nmbr: Cardinal): Boolean; { True if Nmbr is prime, False otherwise. } function StrongProbablePrime(N, B: Cardinal): Boolean; { True if N is a strong probable prime (SPRP) base B. False if N is composite, or if either N or B SPRP base B - Write: N - 1 = 2^s * d, with s = 0 and d odd, then N is probably prime if either: B^d = 1 (mod N), or (B^d)^(2^r) = -1 (mod N), for 0 function WeakProbablePrime(N, B: Cardinal): Boolean; { True if N is a probable prime (PRP) base N. False if N is composite, or if either N or B PRP base B - If B^(N-1) = 1 (mod N), then N is probably prime. } function Pseudoprime(N, B: Cardinal): Boolean; { True if N is a PRP base B, but composite. } function StrongPseudoprime(N, B: Cardinal): Boolean; { True if N is a SPRP base B, but composite. } function IsComposite(Nmbr: Cardinal): Boolean; { True if Nmbr is composite. This isn't equivalent to not IsPrime(Nmbr), because 0 and 1 are neither prime nor composite. } function NextPrime(Nmbr: Cardinal): Cardinal; { Returns the first prime greater than Nmbr, or 0 if the search exceeds MaxPrime. } function PreviousPrime(Nmbr: Cardinal): Cardinal; { Returns the greatest prime less than Nmbr, or 0 if no such prime exists. } function RandomPrime(Range: Cardinal): Cardinal; { Returns a random prime, X, such that 2 2. As with the system function Random, initialise the random number generator by calling Randomize, or assigning a value to RandSeed. Returns 0 for Range function IsJuniorTwin(Nmbr: Cardinal): Boolean; { True if Nmbr and Nmbr 2 are both prime, ie. if they form a 'twin pair'. False if either are composite, or if Nmbr = MaxPrime. } function IsSeniorTwin(Nmbr: Cardinal): Boolean; { True if Nmbr and Nmbr - 2 form a twin pair. } function GapToNextPrime(Nmbr: Cardinal): Cardinal; { Returns the gap to the next prime, where Gap = NextPrime - Nmbr. Returns 0 if the next prime exceeds MaxPrime. } function CountPrimesInRange(P, Q: Cardinal): Cardinal; { Returns the count of primes, X, that satisfy P function RangeContainsPrime(P, Q: Cardinal): Boolean; { True if there is at least one prime, X, that satisfies P Equivalent to CountPrimesInRange(P, Q) 0, but faster. } function IsPrimeFactor(Nmbr, Fctr: Cardinal): Boolean; { True if Fctr is prime, and a factor of Nmbr. } function GetPrimeFactors(Nmbr: Cardinal; var Fctrs: TPrimeFactorArray): Boolean; { True if Nmbr is greater than one. Fctrs will contain Nmbr's prime factors in TPrime records such that p = the factor, q = exponent that p is raised to in factorization of Nmbr. It is not necessary to initialise the array referenced by Fctrs, it will be dynamically sized as required. False if Nmbr is less than two. } function CountPrimeFactors(Nmbr: Cardinal): Word; { Equivalent to summing exponents after a call to GetPrimeFactors. However, this function does not require memory for storing the factors and is faster if you only need to know how many factors there are. } function CountUniqueFactors(Nmbr: Cardinal): Word; { Equivalent to Length(Fctrs) after a call to GetPrimeFactors, but faster. } function LeastPrimeFactor(Nmbr: Cardinal): Cardinal; { Returns the smallest prime factor of Nmbr. 0 if Nmbr is less than 2. If you're only interested in the least prime factor of Nmbr, this function is faster than calling GetPrimeFactors and then examining Fctrs[0]. } function GreatestPrimeFactor(Nmbr: Cardinal): Cardinal; { Returns the greatest prime factor of Nmbr. 0 if Nmbr is less than 2. As with LeastPrimeFactors, this function is faster than calling GetPrimeFactors and then examining Fctrs[High(Fctrs)]. } function GreatestCommonDivisor(P, Q: Cardinal): Cardinal; overload; { Returns the greatest common divisor of P and Q, ie. the largest integer that divides them both. } function GreatestCommonDivisor(Nmbrs: array of Cardinal): Cardinal; overload; { Returns the greatest common divisor of the elements of Nmbrs, ie. the largest integer that divides them all. Returns 0 if Nmbrs has less than two elements. } function LeastCommonMultiple(P, Q: Cardinal): Int64; overload; { Returns the lowest positive integer that both P and Q divide. Returns 0 if either P or Q are 0. } function LeastCommonMultiple(Nmbrs: array of Cardinal): Int64; overload; { Returns the lowest positive integer that can be divided by all elements of Nmbrs. Returns 0 if any element of Nmbrs is 0, or if Nmbrs has less than 2 elements. Note: The LCM of even a small collection of integers can be surprisingly large, and neither version of this function makes any effort to deal with overflows beyond High(Int64). Use with caution. } function AreRelativelyPrime(P, Q: Cardinal): Boolean; { True if P and Q are relatively prime, ie. their greatest common divisor is one. } function AreMutuallyPrime(Nmbrs: array of Cardinal): Boolean; { True if the elements of Nmbrs are mutually relatively prime, ie. if there is no integer that divides them all (other than one). False if Nmbrs are not mutually relatively prime, or if Nmbrs has less than two elements. } function ArePairwisePrime(Nmbrs: array of Cardinal): Boolean; { True if the elements of Nmbrs are pairwise relatively prime, ie. if every unique pairing of elements in Nmbrs is relatively prime. False if Nmbrs are not pairwise relatively prime, or if Nmbrs has less than two elements. } function GetGoldbachPairs(Nmbr: Cardinal; var Primes: TPrimeArray): Boolean; { True if Nmbr is even and greater than 2. The Primes array will be populated with the low members of all prime pairs that satisfy P1 P2 = Nmbr. False if Nmbr is odd or 2. The length of Pairs will be 0. } function CountGoldbachPairs(Nmbr: Cardinal): Cardinal; { If Nmbr is even and greater than 2, returns the number of prime pairs that satisfy P1 P2 = Nmbr. Equivalent to Length(Primes) after a call to GetGoldbachPairs, but faster and without the memory overhead. 0 if Nmbr is odd or 2 (or if Nmbr refutes Goldbach's Conjecture). } function IsGoldbachPair(P, Q: Cardinal): Boolean; { True if both P and Q are prime, and P Q is even and greater than 2. } function HasGoldbachPair(Nmbr: Cardinal): Boolean; { True if Nmbr is even, greater than two and is the sum of two primes, in at least one way. Equivalent to CountGoldbachPairs(Nmbr) 0, but much faster. } implementation { ***************************************** implementation *** } uses Math; {$B-} { ****************************************************** function IsPrime *** } function IsPrime(Nmbr: Cardinal): Boolean; const PF: array[0..8] of Cardinal = (2, 3, 5, 7, 11, 13, 17, 19, 23); var i: Cardinal; begin case Nmbr of 0..1: IsPrime := False; { 0 and 1 are neither prime nor composite. } 2, 7, 61: IsPrime := True; { These three rogues break the test, so deal with them here. } else begin { Do a quick factorisation test using the primes in PF. The list is so short that restricting the factor search to values counter-productive, because of the overhead of calculating the square root. If you extend the factor list, try changing the last inequality in the repeat...until test to (PF[i] Trunc(Sqrt(Nmbr))). } i := 0; repeat Result := ((Nmbr mod PF[i]) 0); Inc(i); until (not Result) or (i High(PF)) or (PF[i] = Nmbr); { If Result is still True, hammer Nmbr with some SPRP tests. If Nmbr prime. } Result := Result and StrongProbablePrime(Nmbr, 2) and StrongProbablePrime(Nmbr, 7) and StrongProbablePrime(Nmbr, 61); end; end; end; { function IsPrime } { ****************************** function MulMod (fast but risky version) *** } function MulMod(x, y, m: Cardinal): Cardinal; { Assumes that on entry: eax = x, edx = y and ecx = m. Self destructs with a #DE (divide error) exception when x and y are large. } asm mul edx div ecx mov eax, edx end; { function MulMod } { ******************************* function MulMod (safe but slow version) *** function MulMod(x, y, m: Cardinal): Cardinal; var i, j: Cardinal; begin i := x mod m; j := y mod m; asm mov eax, i mul j div m mov i, edx end; MulMod := i; end; } { ****************************************** function StrongProbablePrime *** } function StrongProbablePrime(N, B: Cardinal): Boolean; var d, i, r, s: Cardinal; begin if (Min(N, B) 2) then StrongProbablePrime := False else begin { Find d and s that satisfy N - 1 = 2^s * d, where d is odd and s = 0. } d := N - 1; s := 0; while ((d and 1) = 0) do begin d := d shr 1; Inc(s); end; { Use right-to-left binary exponentiation to Calculate B^d mod N, result in i. } i := 1; r := B; while (d 1) do begin if ((d and 1) = 1) then i := MulMod(i, r, N); d := d shr 1; r := MulMod(r, r, N); end; i := MulMod(i, r, N); { If i = 1 or N - 1, then N is a SPRP base B. } Result := (i = 1) or (i = N - 1); { Otherwise, see if i^(2^r) mod N = N - 1, where 0 r := 0; while ((not Result) and (r 1 = s)) do begin i := MulMod(i, i, N); Result := (i = N - 1); Inc(r); end; end; end; { function StrongProbablePrime } { ******************************************** function WeakProbablePrime *** } function WeakProbablePrime(N, B: Cardinal): Boolean; var d, i, r: Cardinal; begin if (Min(N, B) 2) then WeakProbablePrime := False else begin { Use right-to-left binary exponentiation to calculate B^(N-1) mod N, result in i. } d := N - 1; i := 1; r := B; while (d 1) do begin if ((d and 1) = 1) then i := MulMod(i, r, N); d := d shr 1; r := MulMod(r, r, N); end; i := MulMod(i, r, N); WeakProbablePrime := (i = 1); end; end; { function WeakProbablePrime } { ************************************************** function PseudoPrime *** } function Pseudoprime(N, B: Cardinal): Boolean; begin Pseudoprime := WeakProbablePrime(N, B) and IsComposite(N); end; { function Pseudoprime } { ******************************************** function StrongPseudoPrime *** } function StrongPseudoprime(N, B: Cardinal): Boolean; begin StrongPseudoPrime := StrongProbablePrime(N, B) and IsComposite(N); end; { function StrongPseudoprime } { ************************************************** function IsComposite *** } function IsComposite(Nmbr: Cardinal): Boolean; begin { Not quite as trivial as negating IsPrime(Nmbr), but almost. } IsComposite := (Nmbr 3) and (not IsPrime(Nmbr)); end; { function IsComposite } { **************************************************** function NextPrime *** } function NextPrime(Nmbr: Cardinal): Cardinal; begin if (Nmbr = MaxPrime) then { Return 0 as an error result if Nmbr = MaxPrime. } NextPrime := 0 else begin { Deal with Nmbr odd candidates and will therefore miss 2. } if (Nmbr 2) then NextPrime := 2 else begin { Set Result to the next odd number. } Result := Nmbr 1; if ((Result and 1) = 0) then Inc(Result); { Then test consecutive odd numbers until we find a prime. } while (not IsPrime(Result)) do Inc(Result, 2); end; end end; { function NextPrime } { ************************************************ function PreviousPrime *** } function PreviousPrime(Nmbr: Cardinal): Cardinal; begin case Nmbr of { 0, 1 and 2 have no previous prime, so return 0 as an error result. } 0..2: PreviousPrime := 0; { As in NextPrime, the main search only considers odd candidates so Start = 3 is a special case and has to be dealt with separately. } 3: PreviousPrime := 2; else begin { Set Result to the greatest preceding odd number. } Result := Nmbr - 1; if ((Result and 1) = 0) then Dec(Result); { Then test consecutive odd numbers (counting down) until we discover a prime. } while (not IsPrime(Result)) do Dec(Result, 2); end; end; end; { function PreviousPrime } { ************************************************** function RandomPrime *** } function RandomPrime(Range: Cardinal): Cardinal; begin if (Range = 2) then RandomPrime := 0 else repeat Result := NextPrime(Random(Range - 1)); until (Result 0) and (Result ); end; { function RandomPrime } { ************************************************* function IsJuniorTwin *** } function IsJuniorTwin(Nmbr: Cardinal): Boolean; begin Result := (Nmbr ); if Result then Result := IsPrime(Nmbr) and IsPrime(Nmbr 2); end; { function IsJuniorTwin } { ************************************************* function IsSeniorTwin *** } function IsSeniorTwin(Nmbr: Cardinal): Boolean; begin Result := (Nmbr 2); if Result then Result := IsPrime(Nmbr) and IsPrime(Nmbr - 2); end; { function IsSeniorTwin } { *********************************************** function GapToNextPrime *** } function GapToNextPrime(Nmbr: Cardinal): Cardinal; begin Result := NextPrime(Nmbr); if (Result 0) then Result := Result - Nmbr; end; { function GapToNextPrime } { ******************************************* function CountPrimesInRange *** } function CountPrimesInRange(P, Q: Cardinal): Cardinal; begin Result := 0; if (P = Q) then begin if (P 0) then Dec(P); while (P = Q) do begin P := NextPrime(P); Inc(Result); end; Dec(Result); end; end; { function CountPrimesInRange } { ******************************************* function RangeContainsPrime *** } function RangeContainsPrime(P, Q: Cardinal): Boolean; begin RangeContainsPrime := False; if ((P = Q) and (P )) then begin if (P 0) then Dec(P); Result := (NextPrime(P) = Q); end; end; { function RangeContainsPrime } { ************************************************ function IsPrimeFactor *** } function IsPrimeFactor(Nmbr, Fctr: Cardinal): Boolean; begin IsPrimeFactor := ((Nmbr mod Fctr) = 0) and IsPrime(Fctr); end; { function IsPrimeFactor } { ********************************************** function GetPrimeFactors *** } function GetPrimeFactors(Nmbr: Cardinal; var Fctrs: TPrimeFactorArray): Boolean; var i, k: Cardinal; begin SetLength(Fctrs, 0); Result := (Nmbr 1); if Result then begin k := 0; repeat { If Nmbr is prime at this stage, it must be the original Nmbr's greatest prime factor. If Nmbr isn't prime, search for a prime that divides it, starting from two. } if IsPrime(Nmbr) then i := Nmbr else begin i := 2; while ((Nmbr mod i) 0) do i := NextPrime(i); end; { Store the found factor. Append it to Fctrs if it's not already known, otherwise increment the exponent. } if (i k) then begin k := i; SetLength(Fctrs, Length(Fctrs) 1); Fctrs[High(Fctrs)].p := i; Fctrs[High(Fctrs)].q := 1; end else Inc(Fctrs[High(Fctrs)].q); { Divide Nmbr by the last factor found. If this doesn't reduce Nmbr to one then go back to find the lowest prime factor of the new value. } Nmbr := Nmbr div i; until (Nmbr = 1); end; end; { function GetPrimeFactors } { ******************************************** function CountPrimeFactors *** } function CountPrimeFactors(Nmbr: Cardinal): Word; var i: Cardinal; begin Result := 0; while (Nmbr 1) do begin if IsPrime(Nmbr) then i := Nmbr else begin i := 2; while ((Nmbr mod i) 0) do i := NextPrime(i); end; Inc(Result); Nmbr := Nmbr div i; end; end; { function CountPrimeFactors } { ******************************************* function CountUniqueFactors *** } function CountUniqueFactors(Nmbr: Cardinal): Word; var i, j: Cardinal; begin Result := 0; j := 0; while (Nmbr 1) do begin if IsPrime(Nmbr) then begin if (Nmbr j) then Inc(Result); i := Nmbr; end else begin i := 2; while ((Nmbr mod i) 0) do i := NextPrime(i); if (i j) then begin j := i; Inc(Result); end; end; Nmbr := Nmbr div i; end; end; { function CountUniqueFactors } { ********************************************* function LeastPrimeFactor *** } function LeastPrimeFactor(Nmbr: Cardinal): Cardinal; begin if (Nmbr 2) then LeastPrimeFactor := 0 else if IsPrime(Nmbr) then Result := Nmbr else begin Result := 2; while ((Nmbr mod Result) 0) do Result := NextPrime(Result); end; end; { function LeastPrimeFactor } { ****************************************** function GreatestPrimeFactor *** } function GreatestPrimeFactor(Nmbr: Cardinal): Cardinal; begin if (Nmbr 2) then GreatestPrimeFactor := 0 else begin Result := Nmbr; while (not IsPrime(Result)) do Result := Result div LeastPrimeFactor(Result); end; end; { function GreatestPrimeFactor } { **************************************** function GreatestCommonDivisor *** } function GreatestCommonDivisor(P, Q: Cardinal): Cardinal; var i: Cardinal; begin Result := 0; if (Min(P, Q) 0) then begin { Use the Euclidean Algorithm to find the GCD of P and Q. Much faster than calculating the product of the prime factors that they have in common, but much less interesting. } while (Q 0) do begin i := P mod Q; P := Q; Q := i; end; Result := P; end; end; { function GreatestCommonDivisor(P, Q: Cardinal) } function GreatestCommonDivisor(Nmbrs: array of Cardinal): Cardinal; var i, j: Cardinal; begin if (Length(Nmbrs) 2) then Result := 0 else begin j := Low(Nmbrs); Result := GreatestCommonDivisor(Nmbrs[j], Nmbrs[j 1]); for i := j 2 to High(Nmbrs) do Result := GreatestCommonDivisor(Result, Nmbrs[i]); end; end; { function GreatestCommonDivisor(Nmbrs: array of Cardinal) } { ****************************************** function LeastCommonMultiple *** } function LeastCommonMultiple(P, Q: Cardinal): Int64; begin Result := GreatestCommonDivisor(P, Q); if (Result 0) then begin { LCM(a, b) = ab/GCD(a, b) } P := P div Result; LeastCommonMultiple := Int64(P) * Int64(Q); end; end; { function LeastCommonMultiple(P, Q: Cardinal) } function LeastCommonMultiple(Nmbrs: array of Cardinal): Int64; var i, j: Cardinal; begin Result := 0; if (Length(Nmbrs) 1) then begin j := Low(Nmbrs); Result := LeastCommonMultiple(Nmbrs[j], Nmbrs[j 1]); for i := j 2 to High(Nmbrs) do Result := LeastCommonMultiple(Result, Nmbrs[i]); end; end; { LeastCommonMultiple(Nmbrs: array of Cardinal) } { ******************************************* function AreRelativelyPrime *** } function AreRelativelyPrime(P, Q: Cardinal): Boolean; begin AreRelativelyPrime := (GreatestCommonDivisor(P, Q) = 1); end; { function AreRelativelyPrime } { ********************************************* function AreMutuallyPrime *** } function AreMutuallyPrime(Nmbrs: array of Cardinal): Boolean; begin AreMutuallyPrime := (GreatestCommonDivisor(Nmbrs) = 1) end; { function AreMutuallyPrime } { ********************************************* function ArePairwisePrime *** } function ArePairwisePrime(Nmbrs: array of Cardinal): Boolean; var i, j: Cardinal; begin Result := (Length(Nmbrs) 1); if Result then begin i := Low(Nmbrs); while (Result and (i = High(Nmbrs))) do begin j := i 1; while (Result and (j = High(Nmbrs))) do begin Result := (Result and AreRelativelyPrime(Nmbrs[i], Nmbrs[j])); Inc(j); end; Inc(i); end; end; end; { function ArePairwisePrime } { ********************************************* function GetGoldbachPairs *** } function GetGoldbachPairs(Nmbr: Cardinal; var Primes: TPrimeArray): Boolean; var i: Cardinal; begin SetLength(Primes, 0); Result := (Nmbr 2) and ((Nmbr and 1) = 0); if Result then begin { Starting from 2, subtract consecutive primes from Nmbr and check if the result is prime. Stop when Nmbr div 2 is exceeded, because then we're just discovering the reversed versions of the pairs we have already. } i := 2; while (i = (Nmbr div 2)) do begin if IsPrime(Nmbr - i) then begin SetLength(Primes, Length(Primes) 1); Primes[High(Primes)] := i; end; i := NextPrime(i); end; end; end; { function GetGoldbachPairs } { ******************************************* function CountGoldbachPairs *** } function CountGoldbachPairs(Nmbr: Cardinal): Cardinal; var i: Cardinal; begin Result := 0; if ((Nmbr 2) and ((Nmbr and 1) = 0)) then begin i := 2; while (i = (Nmbr div 2)) do begin if IsPrime(Nmbr - i) then Inc(Result); i := NextPrime(i); end; end; end; { function CountGoldbachPairs } { ********************************************** function HasGoldbachPair *** } function HasGoldbachPair(Nmbr: Cardinal): Boolean; var i: Cardinal; begin Result := ((Nmbr 2) and ((Nmbr and 1) = 0)); if Result then begin i := 2; Result := False; while (not Result and (i = (Nmbr div 2))) do if IsPrime(Nmbr - i) then Result := True else i := NextPrime(i); end; end; { function HasGoldbachPair } { *********************************************** function IsGoldbachPair *** } function IsGoldbachPair(P, Q: Cardinal): Boolean; begin IsGoldbachPair := (((P Q) and 1) = 0) and (P Q 2) and IsPrime(P) and IsPrime(Q); end; { function IsGoldbachPair } { ******************************************************* Version History *** To do --------------------------------------------------------------------- Any suggestions? v1.1, 13 June 2002 -------------------------------------------------------- Additions: function WeakProbablePrime function PseudoPrime function StrongPseudoprime function RandomPrime Changes: 01 Moved IsPrime's declaration to make it more obvious. 02 MulMod: Promoted so WeakProbablePrime can use it. 03 Mulmod: 'Safe' version re-written to permit SPRP and PRP with large bases. 04 IsPrime: Shortened the prime factor list. The shorter list seems to be more efficient in combination with the SPRP tests. 05 IsPrime: Scrapped the restriction that (trial factor) With the short prime factor list, calculating Trunc(Sqrt(Nmbr)) is an unwarranted overhead. Also replaced the while loop with a repeat until loop, because it looks neater and doesn't damage performance. v1.0, 12 June 2002 -------------------------------------------------------- Original release. } end. { ********************************************************** THE END *** }