File Coverage

blib/lib/Math/Primality.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Math::Primality;
2             {
3             $Math::Primality::VERSION = '0.08';
4             }
5 9     9   264835 use warnings;
  9         25  
  9         340  
6 9     9   55 use strict;
  9         17  
  9         331  
7 9     9   12874 use Data::Dumper;
  9         124529  
  9         904  
8 9     9   16616 use Math::GMPz qw/:mpz/;
  0            
  0            
9             use base 'Exporter';
10             use Carp qw/croak/;
11             my %small_primes = (
12             2 => 2,
13             3 => 2,
14             5 => 2,
15             7 => 2,
16             11 => 2,
17             13 => 2,
18             17 => 2,
19             19 => 2,
20             23 => 2,
21             29 => 2,
22             31 => 2,
23             37 => 2,
24             41 => 2,
25             43 => 2,
26             47 => 2,
27             53 => 2,
28             59 => 2,
29             61 => 2,
30             67 => 2,
31             71 => 2,
32             73 => 2,
33             79 => 2,
34             83 => 2,
35             89 => 2,
36             97 => 2,
37             101 => 2,
38             103 => 2,
39             107 => 2,
40             109 => 2,
41             113 => 2,
42             127 => 2,
43             131 => 2,
44             137 => 2,
45             139 => 2,
46             149 => 2,
47             151 => 2,
48             157 => 2,
49             163 => 2,
50             167 => 2,
51             173 => 2,
52             179 => 2,
53             181 => 2,
54             191 => 2,
55             193 => 2,
56             197 => 2,
57             199 => 2,
58             211 => 2,
59             223 => 2,
60             227 => 2,
61             229 => 2,
62             233 => 2,
63             239 => 2,
64             241 => 2,
65             251 => 2,
66             257 => 2,
67             );
68              
69             use constant
70             DEBUG => 0
71             ;
72              
73             use constant GMP => 'Math::GMPz';
74              
75             # ABSTRACT: Check for primes with Perl
76              
77              
78              
79             our @EXPORT_OK = qw/is_pseudoprime is_strong_pseudoprime is_strong_lucas_pseudoprime is_prime next_prime prev_prime prime_count/;
80              
81             our %EXPORT_TAGS = ( all => \@EXPORT_OK );
82              
83              
84             sub is_pseudoprime($;$)
85             {
86             my ($n, $base) = @_;
87             return 0 unless $n;
88             $base ||= 2;
89             # we should check if we are passed a GMPz object
90             $base = GMP->new("$base");
91             $n = GMP->new("$n");
92              
93             my $m = GMP->new();
94             Rmpz_sub_ui($m, $n, 1); # $m = $n - 1
95              
96             my $mod = GMP->new();
97             Rmpz_powm($mod, $base, $m, $n ); # $mod = ($base ^ $m) mod $n
98             return ! Rmpz_cmp_ui($mod, 1); # pseudoprime if $mod = 1
99             }
100              
101             # checks if $n is in %small_primes
102             # private functions expect a Math::GMPz object
103             sub _is_small_prime
104             {
105             my $n = shift;
106             $n = Rmpz_get_ui($n);
107             return $small_primes{$n} ? 2 : 0;
108              
109             }
110              
111             sub debug {
112             if ( DEBUG ) {
113             warn $_[0];
114             }
115             }
116              
117              
118             sub is_strong_pseudoprime($;$)
119             {
120             my ($n, $base) = @_;
121              
122             $base ||= 2;
123             $base = GMP->new("$base");
124             $n = GMP->new("$n");
125              
126             # unnecessary but faster if $n is even
127             my $cmp = _check_two_and_even($n);
128             return $cmp if $cmp != 2;
129              
130             my $m = GMP->new();
131             Rmpz_sub_ui($m,$n,1); # $m = $n - 1
132              
133             my ($s,$d) = _find_s_d($m);
134             debug "m=$m, s=$s, d=$d" if DEBUG;
135              
136             my $residue = GMP->new();
137             Rmpz_powm($residue, $base,$d, $n); # $residue = ($base ^ $d) mod $n
138             debug "$base^$d % $n = $residue" if DEBUG;
139              
140             # if $base^$d = +-1 (mod $n) , $n is a strong pseudoprime
141              
142             if ( Rmpz_cmp_ui($residue,1) == 0 ) {
143             debug "found $n as spsp since $base^$d % $n == $residue == 1\n" if DEBUG;
144             return 1;
145             }
146              
147             if ( Rmpz_cmp($residue,$m) == 0 ) {
148             debug "found $n as spsp since $base^$d % $n == $residue == $m\n" if DEBUG;
149             return 1;
150             }
151              
152             map {
153             Rmpz_powm($residue, $residue, GMP->new(2), $n);
154             if (Rmpz_cmp($residue, $m) == 0) {
155             debug "$_:$residue == $m => spsp!" if DEBUG;
156             return 1;
157             }
158             } ( 1 .. $s-1 );
159              
160             return 0;
161             }
162              
163             # given an odd number N find (s, d) such that N = d * 2^s + 1
164             # private functions expect a Math::GMPz object
165             sub _find_s_d($)
166             {
167             my $m = $_[0];
168             my $s = Rmpz_scan1($m,1);
169             my $d = GMP->new();
170             Rmpz_tdiv_q_2exp($d,$m,$s);
171             return ($s,$d);
172             }
173              
174              
175             sub is_strong_lucas_pseudoprime($)
176             {
177             my ($n) = @_;
178             $n = GMP->new("$n");
179             # we also need to handle all N < 3 and all even N
180             my $cmp = _check_two_and_even($n);
181             return $cmp if $cmp != 2;
182             # handle all perfect squares
183             if ( Rmpz_perfect_square_p($n) ) {
184             return 0;
185             }
186             # determine Selfridge parameters D, P and Q
187             my ($D, $P, $Q) = _find_dpq_selfridge($n);
188             if ($D == 0) { #_find_dpq_selfridge found a factor of N
189             return 0;
190             }
191             my $m = GMP->new();
192             Rmpz_add_ui($m, $n, 1); # $m = $n + 1
193              
194             # determine $s and $d such that $m = $d * 2^$s + 1
195             my ($s,$d) = _find_s_d($m);
196             # compute U_d and V_d
197             # initalize $U, $V, $U_2m, $V_2m
198             my $U = GMP->new(1); # $U = U_1 = 1
199             my $V = GMP->new($P); # $V = V_1 = P
200             my $U_2m = GMP->new(1); # $U_2m = U_1
201             my $V_2m = GMP->new($P); # $V_2m = P
202             # initalize Q values (eventually need to calculate Q^d, which will be used in later stages of test)
203             my $Q_m = GMP->new($Q);
204             my $Q2_m = GMP->new(2 * $Q); # Really 2Q_m, but perl will barf with a variable named like that
205             my $Qkd = GMP->new($Q);
206             # start doubling the indicies!
207             my $dbits = Rmpz_sizeinbase($d,2);
208             for (my $i = 1; $i < $dbits; $i++) { #since d is odd, the zeroth bit is on so we skip it
209             # U_2m = U_m * V_m (mod N)
210             Rmpz_mul($U_2m, $U_2m, $V_2m); # U_2m = U_m * V_m
211             Rmpz_mod($U_2m, $U_2m, $n); # U_2m = U_2m mod N
212             # V_2m = V_m * V_m - 2 * Q^m (mod N)
213             Rmpz_mul($V_2m, $V_2m, $V_2m); # V_2m = V_2m * V_2m
214             Rmpz_sub($V_2m, $V_2m, $Q2_m); # V_2m = V_2m - 2Q_m
215             Rmpz_mod($V_2m, $V_2m, $n); # V_2m = V_2m mod N
216             # calculate powers of Q for V_2m and Q^d (used later)
217             # 2Q_m = 2 * Q_m * Q_m (mod N)
218             Rmpz_mul($Q_m, $Q_m, $Q_m); # Q_m = Q_m * Q_m
219             Rmpz_mod($Q_m, $Q_m, $n); # Q_m = Q_m mod N
220             Rmpz_mul_2exp($Q2_m, $Q_m, 1); # 2Q_m = Q_m * 2
221             if (Rmpz_tstbit($d, $i)) { # if bit i of d is set
222             # add some indicies
223             # initalize some temporary variables
224             my $T1 = GMP->new();
225             my $T2 = GMP->new();
226             my $T3 = GMP->new();
227             my $T4 = GMP->new();
228             # this is how we do it
229             # U_(m+n) = (U_m * V_n + U_n * V_m) / 2
230             # V_(m+n) = (V_m * V_n + D * U_m * U_n) / 2
231             Rmpz_mul($T1, $U_2m, $V); # T1 = U_2m * V
232             Rmpz_mul($T2, $U, $V_2m); # T2 = U * V_2m
233             Rmpz_mul($T3, $V_2m, $V); # T3 = V_2m * V
234             Rmpz_mul($T4, $U_2m, $U); # T4 = U_2m * U
235             Rmpz_mul_si($T4, $T4, $D); # T4 = T4 * D = U_2m * U * D
236             Rmpz_add($U, $T1, $T2); # U = T1 + T2 = U_2m * V - U * V_2m
237             if (Rmpz_odd_p($U)) { # if U is odd
238             Rmpz_add($U, $U, $n); # U = U + n
239             }
240             Rmpz_fdiv_q_2exp($U, $U, 1); # U = floor(U / 2)
241             Rmpz_add($V, $T3, $T4); # V = T3 + T4 = V_2m * V + U_2m * U * D
242             if (Rmpz_odd_p($V)) { # if V is odd
243             Rmpz_add($V, $V, $n); # V = V + n
244             }
245             Rmpz_fdiv_q_2exp($V, $V, 1); # V = floor(V / 2)
246             Rmpz_mod($U, $U, $n); # U = U mod N
247             Rmpz_mod($V, $V, $n); # V = V mod N
248             # Get our Q^d calculating on (to be used later)
249             Rmpz_mul($Qkd, $Qkd, $Q_m); # Qkd = Qkd * Q_m
250             Rmpz_mod($Qkd, $Qkd, $n); # Qkd = Qkd mod N
251             }
252             }
253             # if U_d or V_d = 0 mod N, then N is prime or a strong Lucas pseudoprime
254             if(Rmpz_sgn($U) == 0 || Rmpz_sgn($V) == 0) {
255             return 1;
256             }
257             # ok, if we're still here, we have to compute V_2d, V_4d, V_8d, ..., V_{2^(s-1)*d}
258             # initalize 2Qkd
259             my $Q2kd = GMP->new;
260             Rmpz_mul_2exp($Q2kd, $Qkd, 1); # 2Qkd = 2 * Qkd
261             # V_2m = V_m * V_m - 2 * Q^m (mod N)
262             for (my $r = 1; $r < $s; $r++) {
263             Rmpz_mul($V, $V, $V); # V = V * V;
264             Rmpz_sub($V, $V, $Q2kd); # V = V - 2Qkd
265             Rmpz_mod($V, $V, $n); # V = V mod N
266             # if V = 0 mod N then N is a prime or a strong Lucas pseudoprime
267             if(Rmpz_sgn($V) == 0) {
268             return 1;
269             }
270             # calculate Q ^(d * 2^r) for next r (unless on final iteration)
271             if ($r < ($s - 1)) {
272             Rmpz_mul($Qkd, $Qkd, $Qkd); # Qkd = Qkd * Qkd
273             Rmpz_mod($Qkd, $Qkd, $n); # Qkd = Qkd mod N
274             Rmpz_mul_2exp($Q2kd, $Qkd, 1); # 2Qkd = 2 * Qkd
275             }
276             }
277             # otherwise N is definitely composite
278             return 0;
279             }
280              
281             # selfridge's method for finding the tuple (D,P,Q) for is_strong_lucas_pseudoprime
282             # private functions expect a Math::GMPz object
283             sub _find_dpq_selfridge($) {
284             my $n = $_[0];
285             my ($d,$sign,$wd) = (5,1,0);
286             my $gcd = GMP->new;
287              
288             # determine D
289             while (1) {
290             $wd = $d * $sign;
291              
292             Rmpz_gcd_ui($gcd, $n, abs $wd);
293             if ($gcd > 1 && Rmpz_cmp($n, $gcd) > 0) {
294             debug "1 < $gcd < $n => $n is composite with factor $wd" if DEBUG;
295             return 0;
296             }
297             my $j = Rmpz_jacobi(GMP->new($wd), $n);
298             if ($j == -1) {
299             debug "Rmpz_jacobi($wd, $n) == -1 => found D" if DEBUG;
300             last;
301             }
302             # didn't find D, increment and swap sign
303             $d += 2;
304             $sign = -$sign;
305             }
306             # P = 1
307             my ($p,$q) = (1,0);
308             {
309             use integer;
310             # Q = (1 - D) / 4
311             $q = (1 - $wd) / 4;
312             }
313             debug "found P and Q: ($p, $q)" if DEBUG;
314             return ($wd, $p, $q);
315             }
316              
317             # method returns 0 if N < two or even, returns 1 if N == 2
318             # returns 2 if N > 2 and odd
319             # private functions expect a Math::GMPz object
320             sub _check_two_and_even($) {
321             my $n = $_[0];
322              
323             my $cmp = Rmpz_cmp_ui($n, 2);
324             return 1 if $cmp == 0;
325             return 0 if $cmp < 0;
326             return 0 if Rmpz_even_p($n);
327             return 2;
328             }
329              
330              
331             sub is_prime($) {
332             my $n = shift;
333             $n = GMP->new("$n");
334              
335             if (Rmpz_cmp_ui($n, 2) == -1) {
336             return 0;
337             }
338             if (Rmpz_cmp_ui($n, 257) == -1) {
339             return _is_small_prime($n);
340             } elsif ( Rmpz_cmp_ui($n, 9_080_191) == -1 ) {
341             return 0 unless is_strong_pseudoprime($n,31);
342             return 0 unless is_strong_pseudoprime($n,73);
343             return 2;
344             } elsif ( Rmpz_cmp_ui($n, 4_759_123_141) == -1 ) {
345             return 0 unless is_strong_pseudoprime($n,2);
346             return 0 unless is_strong_pseudoprime($n,7);
347             return 0 unless is_strong_pseudoprime($n,61);
348             return 2;
349             }
350             # the strong_pseudoprime test is quicker, do it first
351             return is_strong_pseudoprime($n,2) && is_strong_lucas_pseudoprime($n);
352             }
353              
354              
355             sub next_prime($) {
356             my $n = shift;
357             $n = GMP->new("$n");
358             my $cmp = Rmpz_cmp_ui($n, 2 ); #check if $n < 2
359             if ($cmp < 0) {
360             return GMP->new(2);
361             }
362             if (Rmpz_odd_p($n)) { # if N is odd
363             Rmpz_add_ui($n, $n, 2); # N = N + 2
364             } else {
365             Rmpz_add_ui($n, $n, 1); # N = N + 1
366             }
367             # N is now the next odd number
368             while (1) {
369             return $n if is_prime($n); # check primality of that number, return if prime
370             Rmpz_add_ui($n, $n, 2); # N = N + 2
371             }
372             }
373              
374              
375             sub prev_prime($) {
376             my $n = shift;
377             $n = GMP->new("$n");
378             my $cmp = Rmpz_cmp_ui($n, 3); # compare N with 3
379             if ($cmp == 0) { # N = 3
380             return GMP->new(2);
381             } elsif ($cmp < 0) { # N < 3
382             return undef;
383             } else {
384             if (Rmpz_odd_p($n)) { # if N is odd
385             Rmpz_sub_ui($n, $n, 2); # N = N - 2
386             } else {
387             Rmpz_sub_ui($n, $n, 1); # N = N - 1
388             }
389             # N is now the previous odd number
390             while (1) {
391             return $n if is_prime($n); # check primality of that number, return if prime
392             Rmpz_sub_ui($n, $n, 2); # N = N - 2
393             }
394             }
395             }
396              
397              
398             sub prime_count($) {
399             my $n = shift;
400             $n = GMP->new("$n") unless ref($n) eq 'Math::GMPz';
401             my $primes = 0;
402              
403             return 0 if $n <= 1;
404              
405             do { $primes++ if $n >= $_ } for (2,3,5,7,11,13,17,19,23,29);
406             for (my $i = GMP->new(31); Rmpz_cmp($i, $n) <= 0; Rmpz_add_ui($i, $i, 2)) {
407             next unless 1 == Rmpz_gcd_ui($Math::GMPz::NULL, $i, 3234846615);
408             $primes++ if is_prime($i);
409             }
410              
411             return $primes;
412             }
413              
414              
415             exp(0); # End of Math::Primality
416              
417             __END__