Mercurial > octave
diff scripts/specfun/isprime.m @ 30261:a49c635b179d
isprime.m: Speed up for large integers using Miller-Rabin test (bug #61312).
* libinterp/corefcn/__isprimelarge__.cc: Implement Miller-Rabin test.
* libinterp/corefcn/module.mk: Add new file to build system.
* scripts/specfun/isprime.m: Use new function for large integers.
* scripts/specfun/factor.m: Use "isprime" that is now more efficient.
* NEWS: Add note about performance improvement.
author | Arun Giridhar <arungiridhar@gmail.com> |
---|---|
date | Fri, 29 Oct 2021 20:48:47 -0400 |
parents | 7854d5752dd2 |
children | 168da23530b4 |
line wrap: on
line diff
--- a/scripts/specfun/isprime.m Sat Oct 30 09:47:47 2021 -0400 +++ b/scripts/specfun/isprime.m Fri Oct 29 20:48:47 2021 -0400 @@ -58,9 +58,14 @@ ## @end group ## @end example ## -## Programming Note: @code{isprime} is appropriate if the maximum value in -## @var{x} is not too large (< 1e15). For larger values special purpose -## factorization code should be used. +## Programming Note: @code{isprime} is suitable for all @var{x} +## in the range abs(@var{x}) +## @tex +## $ < 2^{64}$. +## @end tex +## @ifnottex +## < 2^64. +## @end ifnottex ## ## Compatibility Note: @sc{matlab} does not extend the definition of prime ## numbers and will produce an error if given negative or complex inputs. @@ -85,55 +90,84 @@ return; endif - ## Code strategy is to build a table with the list of possible primes - ## and then quickly compare entries in x with the table of primes using - ## lookup(). The table size is limited to save memory and computation - ## time during its creation. All entries larger than the maximum in the - ## table are checked by straightforward division. + ## Code strategy is to quickly compare entries in x with small primes + ## using lookup(), then do direct division on larger numbers up to + ## a threshold, then call Miller-Rabin for numbers over the threshold. + + x = abs (x); # handle negative entries - x = abs (x); # handle negative entries - maxn = max (x(:)); - ## generate prime table of suitable length. - ## 1e7 threshold requires ~0.15 seconds of computation, 1e8 requires 1.8. - maxp = min (maxn, max (sqrt (maxn), 1e7)); - pr = primes (maxp); + ## Generate prime table of suitable length up to maxp. + ## The value of maxp needs to be at least 37, + ## because of the method used by __isprimelarge__ below. + maxp = 37; + pr = [2 3 5 7 11 13 17 19 23 29 31 37]; t = lookup (pr, x, "b"); # quick search for table matches. - ## process any remaining large entries - m = x(x > maxp); - if (! isempty (m)) - if (maxn <= intmax ("uint32")) - m = uint32 (m); - elseif (maxn <= intmax ("uint64")) - m = uint64 (m); - else - warning ("isprime: X contains integers too large to be tested"); - endif + THRESHOLD = 29e9; + ## FIXME: THRESHOLD is the input value at which Miller-Rabin + ## becomes more efficient than direct division. For smaller numbers, + ## use direct division. For larger numbers, use Miller-Rabin. + ## + ## From numerical experiments in Oct 2021, this was observed: + ## THRESHOLD Division Miller-Rabin + ## 380e9 75.0466s 30.868s + ## 100e9 45.0874s 29.1794s + ## 10e9 18.5075s 25.4392s + ## 50e9 31.6317s 27.7251s + ## 25e9 25.1215s 27.004s + ## 38e9 31.4674s 28.1336s + ## 30e9 28.3848s 27.9982s + ## which is close enough to interpolate, so final threshold = 29 billion. + ## + ## The test code was this: + ## n = THRESHOLD - (1:1e7); tic; isprime(n); toc + ## n = THRESHOLD + (1:1e7); tic; isprime(n); toc + ## + ## Two notes for future programmers: + ## + ## 1. Test and tune THRESHOLD periodically. Miller-Rabin is only CPU-limited, + ## while factorization by division is very memory-intensive. This is + ## plainly noticeable from the loudness of the computer fans when running + ## the division technique! CPU speed and RAM speed scale differently over + ## time, so test and tune THRESHOLD periodically. + ## + ## 2. If you make improvements elsewhere in the code that favor one over + ## the other (not symmetric), you should also retune THRESHOLD afterwards. + ## If the Miller-Rabin part is sped up, the optimum THRESHOLD will + ## decrease, and if factorization is sped up, it will increase. + ## - ## Start by dividing through by the small primes until the remaining - ## list of entries is small (and most likely prime themselves). - pr = cast (pr(pr <= sqrt (maxn)), class (m)); - for p = pr + ## Process large entries that are still suitable for direct division + m = x (x > maxp & x <= THRESHOLD); + if ( ! isempty (m)) + ## Start by dividing through by the small primes until the remaining list + ## of entries is small (and most likely prime themselves). + pr2 = primes (sqrt (max (m))); + t |= lookup (pr2, x, "b"); + for p = pr2 m = m(rem (m, p) != 0); if (numel (m) < numel (pr) / 10) break; endif endfor - ## Check the remaining list of possible primes against the - ## remaining prime factors which were not tested in the for loop. - ## This is just an optimization to use arrayfun over for loo - pr = pr(pr > p); - mm = arrayfun (@(x) all (rem (x, pr)), m); + ## Check the remaining list of possible primes against the remaining + ## prime factors which were not tested in the for loop. + ## This is just an optimization to use arrayfun over for loop. + pr2 = pr2 (pr2 > p); + mm = arrayfun (@(x) all (rem (x, pr2)), m); m = m(mm); ## Add any remaining entries, which are truly prime, to the results. - if (! isempty (m)) - m = cast (sort (m), class (x)); - t |= lookup (m, x, "b"); + if ( ! isempty (m)) + t |= lookup (sort (m), x, "b"); endif endif + ## Process remaining entries (everything above THRESHOLD) with Miller-Rabin + ii = (x(:)' > THRESHOLD); + t(ii) = __isprimelarge__ (x(ii)); + endfunction function t = isgaussianprime (z) @@ -162,6 +196,7 @@ %!assert (isprime (3), true) %!assert (isprime (4), false) +%!assert (isprime (uint64 (18446744073709551557)), true) %!assert (isprime (5i), false) %!assert (isprime (7i), true) %!assert (isprime ([1+2i, (2+3i)*(-1+2i)]), [true, false])