This meditation is about the comparision of two simple primality testing algorithms.
Here's how it all started. demerphq raised the question of how you can get a small random prime. The answer to this is that you have to take a random number and check if it's a prime. Thus, we get to primality testings.
The following script has two primality testing algorithms. The first is a trivial one using divisions, the second one is the Rabin-Miller test. This script works only up to 2**31, and for such small numbers the simple one is much faster.
I am quite sure that none of these are the fastest ones available, so if you have a better solution, feel free to suggest it.
Update: I'm also not quite sure that my implementation of the Rabin-Miller test is correct. I did do reasonable efforts to test it, but some branches of it are rarely ran over, so there could be bugs that appear only very rarely. I hope it's correct though.
Here's the code,
#!perl use warnings; use strict; use Inline "C", <<__ENDC; /* mulmod(a, b, m) gives (a*b)%c */ long mulmod (long a, long b, long m) { long long t = (long long)a * b; return (long)(t % m); } __ENDC { # Miller--Rabin test my @smallprimes = ( 2, 3, 5, 7, 11, 13, 17, 19, );
# primep($x) returns whether $x is a prime sub primep_rm { my($cand, $base, $p, $iter, $exp, $odd, $t, $x, $xprev); $cand = 0 + $_[0]; 1 < $cand && $cand == int($cand) or die "error: invalid number to primep_rm: $cand"; for $p (@smallprimes) { 0 != $cand % $p or return $p == $cand; } ($exp, $odd) = (0, $cand - 1); while (0 == $odd % 2) { ($exp, $odd) = ($exp + 1, $odd >> 1); } for $iter (1 .. 52) { $base = 1 + int(rand($cand - 1)); $x = powmod($base, $odd, $cand); SQUARING: for $t (1 .. $exp) { ($xprev, $x) = ($x, mulmod($x, $x, $cand)); $x == 1 and do { $xprev == 1 || $xprev == $cand - 1 or return; last; } } # KEY STEP: $x now contains $base**($cand - 1) % $cand # which should be 1 because of the Fermat theorem. $x == 1 or return; } return 1; }
# powmod($a, $f, $m) calculates $a**$f % $m, # precondition: 1 < $m, 0 < $f, $a are integers representable as a per +l integer sub powmod { my($a, $f, $m) = @_; my $r = 1; for (;;) { 0 != ($f & 1) and $r = mulmod($r, $a, $m); $f >>= 1; 0 == $f and last; $a = mulmod($a, $a, $m); } $r; } } { # simple factoring my @primes; { my($n, $p, $sqrp); NLOOP: for $n (2 .. 100_100) { $sqrp = sqrt($n) + 0.5; for $p (@primes) { $sqrp < $p and last; 0 == $n % $p and next NLOOP; } push @primes, $n } } my $greatest_prime_squared = $primes[-1]**2;
sub primep_div { my $cand = 0 + $_[0]; my($p, $sqr); 1 < $cand && $cand == int($cand) or die "error: invalid number to primep_div: $cand"; $cand < $greatest_prime_squared or die "error: number too large in primep_div: $cand"; $sqr = sqrt($cand) + 0.5; for $p (@primes) { $p <= $sqr or return 1; 0 == $cand % $p and return $p == $cand; } die "internal error: shouldn't fall through here"; }
} if (1) { my $test = sub { my($n, $p_rm, $p_div); $n = $_[0]; $p_rm = !!primep_rm($n); $p_div = !!primep_div($n); $p_rm == $p_div or die "error: primality tests don't match: $n"; }; my($n, $r, $l); warn "begin serial tests"; for $n (2 .. 100000) { &$test($n); } warn "begin random tests"; for $l (3 .. 9) { for $r (1 .. 10000) { $n = 2 + int(rand(10**$l)); &$test($n); } } warn "all ok"; } use Benchmark ":all"; if (1) { warn "start benchmarks"; for my $len (3 .. 9) { my($r_rm, $r_div); my $ntries = $len < 8 ? 50_000 : 20_000; $ntries /= 5; my @tries = map { 10**($len - 1) + 1 + 2 * int(rand((10**$len - 10**($len - 1))/2)); } 0 .. $ntries; warn "benchmarking $len digit odd numbers, $ntries in total"; cmpthese(1, { "primep_rm", sub { my $r; $r += primep_rm($_) ? 1 : 0 for @tries; $r_rm = $r; }, "primep_div", sub { my $r; $r += primep_div($_) ? 1 : 0 for @tries; $r_div = $r; }, }); $r_rm == $r_div or die "error: primality tests don't agree in total"; } } __END__
The results show that the division algorithm is faster.
begin serial tests at p line 125. begin random tests at p line 129. all ok at p line 136. start benchmarks at p line 143. benchmarking 3 digit odd numbers, 10000 in total at p line 151. (warning: too few iterations for a reliable count) (warning: too few iterations for a reliable count) benchmarking 4 digit odd numbers, 10000 in total at p line 151. s/iter primep_rm primep_div primep_rm 11.2 -- -97% primep_div 0.300 3637% -- (warning: too few iterations for a reliable count) (warning: too few iterations for a reliable count) benchmarking 5 digit odd numbers, 10000 in total at p line 151. s/iter primep_rm primep_div primep_rm 10.0 -- -96% primep_div 0.370 2614% -- (warning: too few iterations for a reliable count) (warning: too few iterations for a reliable count) benchmarking 6 digit odd numbers, 10000 in total at p line 151. s/iter primep_rm primep_div primep_rm 9.63 -- -95% primep_div 0.490 1865% -- (warning: too few iterations for a reliable count) (warning: too few iterations for a reliable count) benchmarking 7 digit odd numbers, 10000 in total at p line 151. s/iter primep_rm primep_div primep_rm 9.04 -- -92% primep_div 0.740 1122% -- (warning: too few iterations for a reliable count) (warning: too few iterations for a reliable count) benchmarking 8 digit odd numbers, 4000 in total at p line 151. s/iter primep_rm primep_div primep_rm 8.87 -- -85% primep_div 1.34 562% -- (warning: too few iterations for a reliable count) (warning: too few iterations for a reliable count) benchmarking 9 digit odd numbers, 4000 in total at p line 151. s/iter primep_rm primep_div primep_rm 3.36 -- -68% primep_div 1.08 211% -- (warning: too few iterations for a reliable count) (warning: too few iterations for a reliable count) s/iter primep_rm primep_div primep_rm 3.41 -- -30% primep_div 2.37 44% --
Update: I forgot to mention that I took the R-M algorihm from the Cormen–Leiserson–Rivest–Stein book.
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: Simple primality testing
by demerphq (Chancellor) on Nov 22, 2005 at 23:26 UTC | |
by ambrus (Abbot) on Nov 23, 2005 at 09:45 UTC | |
|
Re: Simple primality testing
by TedYoung (Deacon) on Nov 22, 2005 at 21:41 UTC | |
by ambrus (Abbot) on Jun 18, 2009 at 21:06 UTC | |
|
Re: Simple primality testing
by Jenda (Abbot) on Nov 22, 2005 at 22:30 UTC | |
by blokhead (Monsignor) on Nov 23, 2005 at 00:07 UTC | |
by tilly (Archbishop) on Nov 23, 2005 at 05:13 UTC | |
by ambrus (Abbot) on Nov 23, 2005 at 09:29 UTC | |
by demerphq (Chancellor) on Nov 22, 2005 at 23:30 UTC | |
by Jenda (Abbot) on Nov 23, 2005 at 14:18 UTC | |
|
Re: Simple primality testing
by gaal (Parson) on Nov 22, 2005 at 21:24 UTC | |
by ambrus (Abbot) on Nov 22, 2005 at 21:32 UTC | |
by gaal (Parson) on Nov 23, 2005 at 06:13 UTC | |
by gu (Beadle) on Nov 27, 2005 at 22:12 UTC | |
|
Re: Simple primality testing
by spiritway (Vicar) on Nov 23, 2005 at 05:49 UTC | |
by ambrus (Abbot) on Nov 23, 2005 at 09:47 UTC | |
by Anonymous Monk on Nov 23, 2005 at 16:56 UTC | |
by demerphq (Chancellor) on Nov 24, 2005 at 08:01 UTC | |
by hsmyers (Canon) on Nov 26, 2005 at 15:39 UTC | |
|
Re: Simple primality testing
by ambrus (Abbot) on Oct 20, 2007 at 19:18 UTC | |
|
Re: Simple primality testing
by ambrus (Abbot) on Feb 09, 2009 at 16:53 UTC | |
|
Re: Simple primality testing
by JavaFan (Canon) on Jun 18, 2009 at 21:50 UTC | |
by ambrus (Abbot) on Jun 19, 2009 at 06:01 UTC | |
by JavaFan (Canon) on Jun 19, 2009 at 08:21 UTC |