Mark Engelberg <mark.engelb...@gmail.com> writes:

Hi Mark,

>> For number theory you often need things like
>>
>>    (mod (expt n exp) m)
>
> Yes, and to make things like this fast, the trick is to perform the
> mod at each multiplication step, rather than waiting until the end.

So now I added this suggestion and the test is now really, really fast.
I tried some really big primes from the list of prime numbers on
wikipedia, and it always finishes instantly.  And what's best: the
results seem to be correct, too. ;-)

Here's the code.  Please feel free to comment on where I could do better
or use a more ideomatic style.

--8<---------------cut here---------------start------------->8---
(ns de.tsdh.clojure.math.primes
  ;; TODO: Only expt from math is needed, but how do I use :only with multiple
  ;; libs?
  (:use [clojure.contrib math test-is]))

(def
 #^{:doc "The chances that prime? returns true for a composite if *pseudo*
is true is (expt 4 (* -1 *pseudo-accuracy*))."}
 *pseudo-accuracy* 10)

(defn factorize-out
  "Factorizes out all x factors from n.
Examples:
  (factorize-out 10 2) ==> 5, because 2^1 * 5 = 10
  (factorize-out 90 3) ==> 10, because 3^2 * 10 = 90"
  [n x]
  (loop [acc n exp 0]
    (if (= 0 (mod acc x))
      (recur (/ acc x) (inc exp))
      (hash-map :exponent exp :rest acc))))

(defn expt-mod
  "Equivalent to (mod (expt n e) m), but faster.
http://en.wikipedia.org/wiki/Modular_exponentiation#An_efficient_method:_the_right-to-left_binary_algorithm";
  [n e m]
  (loop [r 1, b n, e e]
    (if (= e 0)
      r
      (recur (if (odd? e)
               (mod (* r b) m)
               r)
             (mod (expt b 2) m)
             (bit-shift-right e 1)))))

(defn prime?
  "Checks if n is a prime using the Miller-Rabin pseudo-primality test.  Also
see *pseudo* and *pseudo-accuracy*."
  [n]
  (cond
    (< n 2)   false
    (= n 2)   true
    (even? n) false
    :else (let [m (factorize-out (dec n) 2)
                d (:rest m)
                s (:exponent m)]
            (loop [k 1]
              (if (> k *pseudo-accuracy*)
                true
                (let [a (+ 2 (rand-int (- n 4)))
                      x (expt-mod a d n)]
                  (if (or (= x 1) (= x (dec n)))
                    (recur (inc k))
                    (if (loop [r 1
                               x (expt-mod x 2 n)]
                          (cond
                           (or (= x 1) (>  r (dec s)))  false
                           (= x (dec n))                true
                           :else (recur (inc r) (mod (* x x) n))))
                      (recur (inc k))
                      false))))))))

(defn next-prime [n]
  "Returns the next prime greater than n."
  (cond
    (< n 2)    2
    :else (loop [n (inc n)]
            (if (prime? n)
              n
              (recur (inc n))))))

;; test stuff

(def
 #^{:private true}
 first-1000-primes
 '(   2      3      5      7     11     13     17     19     23     29 
 ;; [Snipped, but the test works with all 1000 primes...]
   7841   7853   7867   7873   7877   7879   7883   7901   7907   7919))

(deftest test-prime-fns
  (loop [a (take 1000 (iterate next-prime 2)) 
         b (take 1000 first-1000-primes)]
    (when (and (empty? a) (empty? b))
      (is (= (first a) (first b)))
      (recur  (rest a) (rest b)))))
--8<---------------cut here---------------end--------------->8---

Bye,
Tassilo

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To post to this group, send email to clojure@googlegroups.com
To unsubscribe from this group, send email to 
clojure+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/clojure?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to