home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
GEMini Atari
/
GEMini_Atari_CD-ROM_Walnut_Creek_December_1993.iso
/
files
/
language
/
examples
/
primzahl.lsp
< prev
next >
Wrap
Lisp/Scheme
|
1993-10-23
|
41KB
|
1,038 lines
; Bruno Haible 10.11.1987-12.12.1987, 25.2.1990
; Primfaktorzerleger für ganze Zahlen
(provide 'primzahl)
;(in-package 'primzahl)
;(export '(primteiler pfzv pdivisors divisors pfz prim-von-bis
; *Kleinheit* prime probprime notprime factored *pfz-table*
; isprobprime isprime verffz setpfz))
(require 'avl) ; AVL-Bäume
(setq *print-circle* t) ; wegen der zyklischen Listen in fz-other
; (intlist a b) ergibt (a a+1 ... b-1 b), wenn a und b integers sind.
(defun intlist (a b)
(do ((l nil (cons i l))
(i b (1- i)))
((< i a) l)
) )
; exakter Quotient von Integers, schneller als / :
#-CLISP
(defun exquo (a b)
(multiple-value-bind (q r) (floor a b)
(unless (zerop r) (error "Quotient ~S/~S nicht exakt." a b))
q
) )
(require 'jacobi)
; (jacobi a b) liefert für ganze Zahlen a,b mit ungeradem b>0 das Jacobi-Symbol
; (a / b). Es ist =0 genau dann, wenn a und b nicht teilerfremd sind.
; (pfz n) liefert die Primfaktorzerlegung von n>0, die Primfaktoren von n
; in aufsteigender Reihenfolge, evtl. mehrfach vertreten.
#|
; vorläufige Version des Primfaktorzerlegers
(defun pfz (n)
(reverse
(let ((l nil)) ; l = Liste der bereits gefundenen Faktoren
(do () ((oddp n))
(setq n (exquo n 2))
(push 2 l))
(let ((p 3)) ; p = Test-teiler (ungerade)
(loop
(if (= n 1) (return l))
(setq s (isqrt n))
(do () ((or (> p s) (zerop (mod n p))))
(incf p 2))
(if (> p s) (return (cons n l)))
(push p l)
(setq n (exquo n p))
) ) ) ) )
|#
; (Miller-Rabin-test N) testet, ob eine gegebene Zahl n>1 wohl eine
; Primzahl ist.
; Falls das Ergebnis NIL ist, ist N garantiert zusammengesetzt,
; Eventuell kommt ein nichttrivialer Teiler von N als zweiter Wert heraus.
; Falls das Ergebnis T ist, ist N mit hoher Wahrscheinlichkeit prim.
; (Fehlerwahrscheinlichkeit < 1E-30 )
; Ist n gerade und >2, so ist das Ergebnis NIL.
(defun miller-rabin-test (n)
; Vorausschleife, die bereits 87% aller Zahlen faktorisiert
(dolist (p '(2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67))
(if (zerop (mod n p))
(return-from miller-rabin-test (if (= n p) t (values nil p)))))
; erst jetzt geht's richtig los:
(let (blist n1 s t1)
(if (< n 2) (return-from miller-rabin-test nil))
(setq blist (cond ((< n 2000) '(2))
((< n 1300000) '(2 3))
((< n 25000000) '(2 3 5))
((< n 3200000000) '(2 3 5 7))
(t '(2 3 5 7 11 13 17 19 23 29
31 37 41 43 47 53 59 61 67 71
73 79 83 89 97 101 103 107 109 113
127 131 137 139 149 151 157 163 167 173
179 181 191 193 197 199 211 223 227 229))))
; mit 50 Primzahlen: P(falsch) < (1/4)^50 < 1E-30
(setq n1 (- n 1))
(setq s n1 t1 0)
(do () ((oddp s))
(setq s (exquo s 2)) (incf t1))
; n-1 = s * 2^t1 mit ungeradem s zerlegt.
(dolist (b blist)
(if (zerop (mod n b))
(if (= n b) (return-from miller-rabin-test t)
(return-from miller-rabin-test (values nil b))))
(do ((c (exptmod (mod b n) s n) (sqrmod c n))
(c1 n1 c)
(j 0 (1+ j)))
((or (= j t1) (= c 1))
(cond ((not (= c 1)) ; sicher j=t1, aber c/=1
(return-from miller-rabin-test nil))
((not (= c1 n1)) ; sicher c=1, c1/=n-1, also j>0
; ggt(c1-1,n) * ggt(c1+1,n) = ggt(c1^2-1,n) = ggt(c-1,n) = n
(return-from miller-rabin-test (values nil (gcd n (- c1 1)))))
) )) ; Eine Iteration über b beendet
) ; blist abgearbeitet
t ; n wahrscheinlich prim
) )
; Hilfsfunktion für Miller-Rabin-Test:
; Quadrieren modulo n von x mit 0<=x<n
(defun sqrmod (x n)
(mod (* x x) n))
; Hilfsfunktion für Miller-Rabin-Test:
; Potenzieren modulo n von a mit 0<=a<n und b>0
(defun exptmod (a b n)
(let ((c 1))
(loop ; a^b*c mod n bleibt invariant
(if (oddp b) (setq c (mod (* a c) n)))
(if (= b 1) (return c))
(setq a (mod (* a a) n) b (floor b 2))
) ) )
; verifizierter Primzahltest für kleine Zahlen n>0
(defun klprimtest (n)
(cond ((= n 1) nil)
((evenp n) (= n 2))
(t (let ((s (isqrt n)))
(do ((i 3 (+ i 2)))
((> i s) t)
(if (zerop (mod n i)) (return nil))
) ) ) ) )
; Primfaktorzerlegung kleiner Zahlen n>0
(defun klpfz (n)
(let ((l nil))
(do () ((oddp n)) (setq n (ash n -1)) (push 2 l)) ; Zweier heraus
(block Rest-prim
(do ((p 3))
((= n 1))
(loop ; in dieser Schleife wird ein Teiler gesucht
(cond ((> (* p p) n) (return-from Rest-prim (push n l)))
((zerop (mod n p)) (push p l) (setq n (exquo n p)) (return))
(t (incf p 2))
) ) ) )
(nreverse l)
) )
; Datenstruktur: In *pfz-table* steht eine Tabelle von Primfaktorzerlegungs-
; ergebnissen. Zu jeder Zahl (sie ist der Schlüssel) steht drin:
; Ob sie Primzahl ist, eine eventuelle Faktorzerlegung bzw. evtl. eine
; Primitivwurzel.
(defparameter *Kleinheit* 10000
"Nur Zahlen >= *Kleinheit* werden in die PFZ-Tabelle aufgenommen.")
; *Kleinheit* sollte nur geändert werden, wenn gleichzeitig
; (setq *pfz-table* nil) ausgeführt wird!
(assert (> *Kleinheit* 1))
(deftype fz-class () "Das Ergebnis einer Faktorzerlegung, ganz grob"
'(member ; ein Symbol:
nil ; (nur kurz nach der Initialisierung)
prime ; n ist prim
probprime ; n ist wahrscheinlich prim
notprime ; n ist garantiert zusammengesetzt, Faktoren unbekannt
factored ; n ist zusammengesetzt, schon faktorisiert
) )
(defstruct fz "Eine Faktorisierung als Ganzes"
(num 2 :type integer :read-only t) ; die zu faktorisierende Zahl, >1
(cl nil :type fz-class) ; Klassifikation
(other nil) ; weitere Information:
; bei cl = prime : NIL oder eine Primitivwurzel mod n
; bei cl = probprime : Eine zyklische Liste von wartenden Funktionen.
; Jede von ihnen liefert NIL, wenn sie wieder aufge-
; rufen werden will, und (NIL . factor), falls sie
; die Zahl als zusammengesetzt nachgewiesen hat, evtl.
; einen nichttrivialen Faktor gefunden hat, und
; (T . Prw), falls sie die Zahl als prim nachgewiesen
; hat, mit evtl. einer Primitivwurzel Prw.
; (Eventuell zu Beginn die leere Liste.)
; bei cl = notprime : Eine zyklische Liste von wartenden Funktionen
; (Closures), die mitten in der Suche nach einem
; Faktor von n stecken. Jede von ihnen liefert NIL,
; wenn sie wieder aufgerufen werden will, und einen
; nichttrivialen Faktor, wenn sie einen gefunden hat.
; (Eventuell zu Beginn die leere Liste.)
; bei cl = factored : Ein Konstrukt vom Typ fzl, das die Faktorenzerlegung
; von n widerspiegelt.
)
(defstruct fzl "Faktorenzerlegung, Liste"
(allprimes nil :type symbol) ; Flag, ob alle Faktoren von factorlist
; bekanntermaßen Primzahlen sind.
(factorlist nil :type list) ; Eine Liste (f1 ... fk) von Faktoren von n,
; mit 1 < f1 <= ... <= fk und n = f1 * ... * fk. Die fj sind dabei
; (Pointer auf) Konstrukte vom Typ fz, bzw. bei fj<*Kleinheit* ist fj
; die Zahl selbst und Primzahl.
)
; (fz-key f) ergibt zu einem Element einer Faktorzerlegungsliste die
; wirkliche Zahl.
(defun fz-key (g)
(if (integerp g) g (fz-num g))
)
; (fz-isprime f) stellt fest, ob ein Element einer Faktorzerlegungsliste
; prim ist.
(defun fz-isprime (f)
(or (integerp f) (eq (fz-cl f) 'prime))
)
; (mergefz l1 l2) mischt zwei Faktorzerlegungen l1 und l2 von n1 bzw. n2,
; so daß eine Faktorzerlegung von n1*n2 herauskommt. Vorsicht: Destruktiv!
(defun mergefz (l1 l2)
(merge 'list l1 l2 #'< :key #'fz-key)
)
; (fzl-recalc h) berechnet neu, ob alle Faktoren von h (h ist vom Typ fzl)
; prim sind, trägt diese Information in h ein und liefert sie als Ergebnis.
(defun fzl-recalc (h)
(setf (fzl-allprimes h)
(reduce #'(lambda (allp f) (and allp (fz-isprime f)))
(fzl-factorlist h)
:initial-value t
) ) )
(defun fz-comp (x y) (< (fz-num x) (fz-num y)))
(defun fz-eq-test (x y) (= (fz-num x) (fz-num y)))
(defvar *pfz-table* (avl:seq-to-avl nil #'fz-comp)
"Die globale Tabelle, die alle Primfaktorzerlegungsergebnisse enthält")
; Die in *pfz-table* abgespeicherten Werte sind alle vom Typ fz.
; Die Ordnungsrelation ist durch die Zahl selbst (fz-num *) gegeben.
; Ausgeben der globalen Tabelle:
(defun pt (&optional (output-stream *standard-output*))
(pprint (avl:avl-to-seq *pfz-table*) output-stream)
)
(defun pfz-table-lookup (n)
; ergibt die in *pfz-table* stehende Faktorzerlegung von n
; 1. Version (langsamer):
; (avl:member (make-fz :num n) *pfz-table* #'fz-comp
; #'(lambda (x y) (and (fz-eq-test x y) y)) ))
; 2. Version (schneller, aber weniger portabel):
(do ((tr *pfz-table*))
((or (null tr) (= n (fz-num (avl::node-value tr))))
(cond (tr (avl::node-value tr))))
(cond ((< n (fz-num (avl::node-value tr))) (setq tr (avl::node-left tr)))
((> n (fz-num (avl::node-value tr))) (setq tr (avl::node-right tr)))
(t (error "Unvergleichbarkeit zweier Zahlen!"))
) ) )
; (pfz-table-insert b) fügt zu *pfz-table* einen neuen Eintrag b (vom Typ fz)
; hinzu. Ergebnis ist b.
(defun pfz-table-insert (b)
(setq *pfz-table* (avl:insert b *pfz-table* #'fz-comp #'fz-eq-test))
b
)
; (pfz-table-lookup-insert n) sucht in *pfz-table* nach einem Eintrag zur Zahl
; n. Wird kein Eintrag gefunden, so wird ein neuer in die Tabelle eingefügt.
; Das Ergebnis ist stets der entsprechende Eintrag in der Tabelle *pfz-table*.
(defun pfz-table-lookup-insert (n)
(assert (>= n *Kleinheit*))
(let ((b (pfz-table-lookup n)))
(unless b (pfz-table-insert (setq b (make-fz :num n))))
b
) )
; (recall-factors n) ergibt eine Liste aller Faktoren der natürlichen Zahl n>1,
; soweit sie bekannt sind, in einer sortierten Liste, im selben Format wie bei
; fzl. Der zweite Wert zeigt an, ob alles sicher Primzahlen sind.
(defun recall-factors (n)
(if (< n *Kleinheit*)
(values (klpfz n) t)
(let ((b (pfz-table-lookup n)))
(if (null b)
; nicht gefunden
(progn
(mr n)
(values (list (pfz-table-lookup n)) nil)
)
; gefunden
(case (fz-cl b)
((prime) (values (list b) t))
((probprime notprime) (values (list b) nil))
((factored)
(setq b (fz-other b))
(values (fzl-factorlist b) (fzl-allprimes b))
)
(t (error "Falsch aufgebauter fz-Struct!"))
) ) ) ) )
; (build-fzl l) ergibt die Faktorzerlegung vom Typ fzl von n, wenn bekannt ist,
; daß das Produkt aller Elemente von l (alles natürliche Zahlen >1) = n ist.
(defun build-fzl (l)
(let ((l1 nil) (allp t))
(dolist (f l) (multiple-value-bind (f1 f2) (recall-factors f)
(setq l1 (mergefz l1 (copy-list f1)))
(setq allp (and allp f2))
) )
(make-fzl :allprimes allp :factorlist l1)
) )
; Führt für eine Zahl >= *Kleinheit*, über die noch nichts bekannt ist,
; den Miller-Rabin-Test durch und speichert das Ergebnis in *pfz-table* ab.
; Bei Ergebnis t (also n wohl prim) ist n=2 oder n>2 ungerade.
(defun mr (n)
(multiple-value-bind (pr fct) (miller-rabin-test n)
(cond (pr ; wohl prim
(pfz-table-insert (make-fz :num n :cl 'probprime))
t)
; sonst sicher nicht prim
(fct ; Faktor gefunden
(pfz-table-insert
(make-fz :num n :cl 'factored
:other (build-fzl (list fct (exquo n fct)))))
nil)
(t ; kein Faktor gefunden
(pfz-table-insert (make-fz :num n :cl 'notprime))
nil)
) ) )
; (isprobprime n) stellt fest, ob die natürliche Zahl n wahrscheinlich eine
; Primzahl ist.
; Bei geraden Zahlen >2 liefert dies stets NIL.
(defun isprobprime (n)
(if (< n *Kleinheit*)
(klprimtest n)
(let ((b (pfz-table-lookup n)))
(if b (member (fz-cl b) '(prime probprime)) ; bekannt
; sonst muß gerechnet werden:
(mr n)
) ) ) )
; (isprime n) stellt fest, ob die natürliche Zahl n eine Primzahl ist.
(defun isprime (n)
(if (< n *Kleinheit*)
(klprimtest n)
(let ((b (pfz-table-lookup n)))
(if (and b (member (fz-cl b) '(prime))) ; bekannt
(return-from isprime t))
(if (and b (member (fz-cl b) '(notprime factored))) ; bekannt
(return-from isprime nil))
(assert (or (null b) (eq (fz-cl b) 'probprime)))
(if (and (null b) (null (mr n))) (return-from isprime nil))
; jetzt ist bekannt, daß n wohl prim ist, steht in *pfz-table*.
(if (null b) (setq b (pfz-table-lookup n)))
(assert (and b (member (fz-cl b) '(prime probprime))))
(if (eq (fz-cl b) 'prime) (return-from isprime t))
(verffz b) ; Primzahlverdacht erhärten
(assert (not (eq (fz-cl b) 'probprime)))
(eq (fz-cl b) 'prime)
) ) )
(defparameter nearly-infinite 1000000000000000000
"Schier unendliche Zahl von Iterationen")
; Sei (fz-cl b) = 'probprime und (fz-other b) /= NIL.
; (tryprimeprove b) ruft die wartenden Funktionen in (fz-other b) der Reihe
; nach auf, bis ein Ergebnis kommt. Maximal k Iterationen (k fehlt->grenzenlos).
; Das Ergebnis ist die Anzahl der verbrauchten Iterationen.
(defun tryprimeprove (b &optional (k nearly-infinite) &aux (it 0) erg)
(loop
(when (>= it k) (return it))
(incf it)
(when (setq erg (funcall (first (fz-other b))))
; Ungewißheit beendet.
; erg = (NIL) oder (NIL . factor) oder (T) oder (T . Prw)
(cond ((car erg) (setf (fz-cl b) 'prime)
(setf (fz-other b) (cdr erg)) )
((null (cdr erg)) (setf (fz-cl b) 'notprime)
(setf (fz-other b) nil) )
(t (setq erg (cdr erg))
(setf (fz-other b) nil)
(setf (fz-cl b) 'factored)
(setf (fz-other b)
(build-fzl (list erg (exquo (fz-num b) erg))))
) )
(return it)
)
(pop (fz-other b)) ; Funktionen-Schlange rotieren
) )
; 1. Primzahlbeweiser: Bei jedem Aufruf wird eine gewisse Anzahl von
; Teilern durchprobiert, maximal bis zur Wurzel.
; [Allgemein bekannt.]
(defun tryprimeprover1 (n)
(let ((p 1)
(s (isqrt n)))
#'(lambda ()
(block tryprimeproving1
(dotimes (i (min 100 (ceiling (- s p) 2)))
(incf p 2)
(if (zerop (mod n p))
(return-from tryprimeproving1 (cons nil p)))
)
(if (>= p s) (cons t nil) nil)
) ) ) )
; 2. Primzahlbeweiser: Aus einer hinreichend guten Faktorzerlegung von n-1
; wird eine Zahl der Ordnung n-1 mod n bestimmt, also ist n prim.
; [Donald E. Knuth, The art of computer programming, Band 2, Seminumerical
; algorithms, Abschnitt 4.5.4, Aufgabe 26, Seite 397.]
(defun tryprimeprover2 (n)
(let ((enough nil)
f r plist
(n1 (1- n)))
(flet ((factored-enough (b) ; stellt fest, ob n-1 weit genug faktorisiert
(setq enough ; ist.
(and (= (fz-num b) n1)
(eq (fz-cl b) 'factored)
(progn
; f sammelt die primen Faktoren p von n,
; r sammelt die nichtprimen Faktoren von n,
; plist = Menge aller primen Faktoren von n.
(setq f 1)
(setq r 1)
(setq plist nil)
(dolist (p (fzl-factorlist (fz-other b)))
(if (fz-isprime p)
(progn
(setq f (* f (fz-key p)))
(setq plist (adjoin (fz-key p) plist))
)
(setq r (* r (fz-key p)))
) )
; alles ok, wenn r<=f+1
(<= r (1+ f))
) ) ) )
(tpp2-rest () ; führt den Primzahlbeweis aus, wenn f,r,plist da.
; Bei r=1 könnte man sogar eine Primitivwurzel errechnen.
; [D. E. Knuth, Band 2, Abschnitt 4.5.4, Aufgabe 10, Seite 395.]
(do ((x 2 (1+ x)))
((null plist) (cons t nil))
(unless (= (exptmod x n1 n) 1)
(format t
"~%Bei ~D hatte der Miller-Rabin-Test falsch geraten!~%"
n)
(return (cons nil nil))
)
(setq plist
(remove-if #'(lambda (p)
(= (gcd (1- (exptmod x (exquo n1 p) n)) n) 1) )
plist
)) ) ) )
(if (< n1 *Kleinheit*)
#'(lambda ()
(setq f n1)
(setq r 1)
(setq plist (primteiler n1))
(tpp2-rest))
(let ((b (pfz-table-lookup n1)))
(when (null b)
(isprobprime n1) ; n1 in *pfz-table* eintragen
(setq b (pfz-table-lookup n1)) )
#'(lambda ()
(verffz b 1 :return-test #'factored-enough)
; Anmerkung: Immer nur eine Zeitscheibe ist wohl ungünstig,
; weil dann stets der kleinste mögliche Faktor von n1
; verfeinert wird.
(when (or enough (factored-enough b))
(tpp2-rest)
) )
) ) ) ) )
; Sei (fz-cl b) = 'notprime und (fz-other b) /= NIL.
; (tryfactor b) ruft die wartenden Funktionen in (fz-other b) der Reihe nach
; auf, bis ein Ergebnis kommt. Maximal k Iterationen (k fehlt -> grenzenlos).
; Ergebnis ist die Anzahl der verbrauchten Iterationen.
(defun tryfactor (b &optional (k nearly-infinite) &aux (it 0) erg)
(loop
(when (>= it k) (return it))
(incf it)
(when (setq erg (funcall (first (fz-other b))))
(setf (fz-other b) nil)
(setf (fz-cl b) 'factored)
(setf (fz-other b) (build-fzl (list erg (exquo (fz-num b) erg))))
(return it))
(pop (fz-other b)) ; Funktionen-Schlange rotieren
) )
; Alle Faktorisierungsalgorithmen bekommen eine ungerade Zahl n>1, die
; garantiert zusammengesetzt ist, und liefern eine parameterlose Funktion ab,
; die nach wiederholtem Aufruf einen Faktor von n liefert (und NIL, solange
; sie noch keinen Faktor gefunden hat).
; erster Faktorisierungsalgorithmus: Teilersuche bis zur Wurzel
; [Allgemein bekannt.]
(defun tryfactor1 (n)
(let ((p 1)
(s (isqrt n)))
#'(lambda ()
(block tryfactoring1
(dotimes (i (min 100 (ceiling (- s p) 2)))
(incf p 2)
(if (zerop (mod n p)) (return-from tryfactoring1 p))
)
(if (>= p s) (error "Nicht-Primzahl ~D ohne Teiler." n))
nil
) ) ) )
; zweiter Faktorisierungsalgorithmus: Pollardsche Iteration, in der Version
; von R.P. Brent.
; [Richard P. Brent, An improved Monte Carlo factorization algorithm,
; BIT 20 (1980), 176-184, Seite 182].
(defun tryfactor2 (n &aux (m 100))
(let ((state 1) ; Zustand: 1 bei der Initialisierung,
; 2 bei den Iterationen ins Leere,
; 3 bei den Iterationen in Blöcken.
(s 1) ; Iterationsparameter
x ; relative Anfangszahl
y ; Iterationswert
r ; wird immer wieder verdoppelt
k ; Anzahl der bisher ausgeführten Iterationen (0<=k<=r)
q ; bisher aufgelaufenes Produkt
) ; m = Blocklänge
(flet ((brentiter (x)
(mod (+ (* x x) s) n)
))
#'(lambda ()
(let ((it 100)) ; Noch verbleibende Iterationen
(loop
(if (<= it 0) (return nil))
(case state
((1) (setq y 0)
(setq x y)
(setq r 1)
(setq k 0)
(setq q 1)
(setq state 2)
)
((2) (let ((i (min it (- r k))))
(dotimes (i1 i) (setq y (brentiter y)))
(incf k i)
(decf it i))
(when (= k r)
(setq k 0)
(setq state 3)
) )
((3) (let ((ys y)
(i (min it (- r k) m))
g)
(dotimes (i1 i)
(setq y (brentiter y))
(setq q (mod (* q (abs (- x y))) n))
)
(incf k i)
(decf it i)
(setq g (gcd q n))
(cond ((> g 1) ; Habe wohl etwas gefunden
(if (= g n) ; wirklich?
(loop ; nachiterieren
(setq ys (brentiter ys))
(decf it)
(setq g (gcd (- x ys) n))
(if (> g 1) (return))
) )
(if (< g n) ; ja!
(return g))
; nein ...
(incf s) ; ganz neuer Versuch
(setq state 1)
)
((= k r) ; state 3 zu Ende
(setq x y)
(setq r (* 2 r))
(setq k 0)
(setq state 2)
) ) ))
) ) ) ) ) ) )
; dritter Faktorisierungsalgorithmus: Fermats Methode.
; [Donald E. Knuth, The art of computer programming, Band 2, Seminumerical
; algorithms, Abschnitt 4.5.4, Algorithmus C, Seite 371.]
(defun tryfactor3 (n)
(let* ((s (isqrt n))
(x (1+ (* 2 s)))
(y 1)
(r (- (* s s) n)) )
#'(lambda ()
(dotimes (it 500)
(cond ((> r 0) (decf r y) (incf y 2))
((< r 0) (incf r x) (incf x 2))
(t (assert (zerop r)) (return (exquo (- x y) 2)))
) ) ) ) )
#|
; vierter Faktorisierungsalgorithmus: Siebmethode.
; [Donald E. Knuth, The art of computer programming, Band 2, Seminumerical
; algorithms, Abschnitt 4.5.4, Algorithmus D, Seite 373.]
(defun tryfactor4 (n)
(let* ((x (isqrt n))
(mlist '#(3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61))
(r (length mlist))
|#
; fünfter Faktorisierungsalgorithmus: Kettenbruch.
; Auch Algorithmus von Kraitchik - Morrison - Brillhart genannt.
; [Donald E. Knuth, The art of computer programming, Band 2, Seminumerical
; algorithms, Abschnitt 4.5.4, Algorithmus E, Seiten 380-384.]
; [Carl Pomerance: The quadratic sieve factoring algorithm, in:
; Lecture Notes in Computer Science, Band 209, Advances in Cryptology,
; Seiten 169-173.]
; [Marvin C. Wunderlich: A running time analysis of Brillhart's continued
; fraction factoring method, in:
; Lecture Notes in Mathematics, Band 751, Seiten 328-342.]
(defun tryfactor5 (n)
(let* ((m (floor (exp (* 0.25 (sqrt (* (log n) (log (log n))))))))
; man kann den Faktor 0.25 auch vergrößern, wenn der Platz reicht.
(factorbase (make-array (list m) :element-type 'integer))
; wird ein Array von m Primzahlen sein
(eqnlist nil) ; wird eine Liste von Gleichungen sein, wobei die Array-
; Komponenten in Gaußscher Treppennormalform über
; GF(2) sind.
)
; (assert (>= m 1))
(flet ((base-factor (v) ; stellt fest, ob abs(v) ganz über der Faktorbasis
; zerfällt. Wenn ja, also v = (-1)^e0 * ... * pm^em * v1^2, kommt
; ( #(e0 ... em) . v1) als Ergebnis.
(block base-factoring
(let ((a (make-array (list (1+ m)) :initial-element 0)))
(assert (not (zerop v)))
(when (minusp v) ; Ziehe Faktor (-1) heraus
(setq v (- v))
(setf (aref a 0) 1)
)
(dotimes (i m) ; Ziehe kleine Faktoren heraus
(let ((p (aref factorbase i)))
(do ()
((cond ((zerop (mod v p))
(setq v (exquo v p))
(incf (aref a (1+ i)))
nil)
(t)
) ) ) ))
(unless (= v 1) ; Ist der Rest ein Quadrat?
(let ((v2 (isqrt v)))
(if (= (* v2 v2) v)
(setq v v2)
(return-from base-factoring nil) ; nein -> wertlos
) ) )
(cons a v)
) ) )
(add-eqn (eqn) ; fügt eine neue Gleichung eqn = (#(e0..em) x . v)
; die x^2 = (-1)^e0 ... pm^em v^2 mod n bedeutet,
; zum Wissen in eqnlist hinzu.
; Wird ein nichttrivialer Faktor von n gefunden, erfolgt ein THROW.
(block add-eqn
(do ((l1 nil)
(l2 eqnlist)
h ha a)
; stets (append (reverse l1) l2) = eqnlist
((endp l2) ; Liste eqnlist zu Ende untersucht.
(if ; Sind in eqn alle Exponenten gerade?
(some #'oddp (car eqn)) ; Nein: muß eqn behalten
(setq eqnlist (nreconc l1 (list eqn)))
; Ja! Ist die Gleichung x^2 = y^2 mod n trivial ?
(let ((x (second eqn))
(y (let ((v (cddr eqn)))
(dotimes (i m)
(setq v (* v
(expt (aref factorbase i)
(exquo (aref (car eqn) (1+ i)) 2)
) )))
v
)) )
(assert (zerop (mod (- (* x x) (* y y)) n)))
(unless (or (zerop (mod (- x y) n))
(zerop (mod (+ x y) n)))
; Nein! Fertig!
(throw 'tryfactoring5 (gcd (- x y) n))
)) ) )
(setq h (car l2)) ; Hilfsgleichung aus der Liste
; Falls h und eqn an derselben Stelle ihre erste ungerade Zahl
; haben, wird h zu eqn multipliziert:
(setq ha (car h)) (setq a (car eqn))
(if (do ((i 0 (1+ i)))
((> i m) nil)
(cond ((and (oddp (aref ha i)) (evenp (aref a i)))
(return nil))
((and (evenp (aref ha i)) (oddp (aref a i)))
; muß eqn vor h einfügen
(setq eqnlist (nreconc l1 (cons eqn l2)))
(return-from add-eqn nil))
((and (oddp (aref ha i)) (oddp (aref a i)))
(return t))
) )
(progn
(dotimes (i (1+ m))
(incf (aref a i) (aref ha i))
)
(setq eqn (cons a
(cons (mod (* (second eqn) (second h)) n)
(mod (* (cddr eqn) (cddr h)) n)
) ) ))
)
(pop l2)
(push h l1)
) ) )
(calc-factorbase (D) ; berechnet zu D=k*n eine passende Faktorbasis
(do ((i 0)
(p 2 (1+ p)))
((>= i m))
(when (or (= p 2) (and (klprimtest p) (= (jacobi D p) 1)))
(setf (aref factorbase i) p)
(incf i)
) ) )
)
(let ((D N) ; D=k*n
(Anfangszustand t)
R R1 U U1 V V1 P P1 A S ; Variablenbenennung wie in [Knuth]
; Mit den Bezeichnungen wie in [Wunderlich]:
; Es gilt für alle i=0,1,...:
; sqrt(D) = [q0,...,qi-1,(sqrt(D)+Pi)/Qi]
; qi = floor((sqrt(D)+Pi)/Qi) = floor((floor(sqrt(D))+Pi)/Qi),
; Q-1 = D - q0^2, P0=0, Q0=1, Pi+1 = qi * Qi - Pi,
; Qi+1 = (D - Pi+1)^2 / Qi = Qi-1 - qi * ( qi * Qi - 2 * Pi ),
; alle abs(Pi)<sqrt(D), alle Qi>0, alle qi>0, alle Variablen ganz.
; A-1=1, B-1=0, A0=q0, B0=1,
; Ai = qi * Ai-1 + Ai-2, Bi = qi * Bi-1 + Bi-2 für i>0,
; in Q(X) gilt (Ai+x*Ai-1)/(Bi+x*Bi-1) = [q0,...,qi-1,qi + x].
; Für i>=0 ist Ai-1^2 - D * Bi-1^2 = (-1)^i * Qi
; Für i>=0 ist Ai-1 * Ai - D * Bi-1 * Bi = (-1)^i * Pi+1
; Nach i Schleifendurchläufen ist
; R = floor(sqrt(D))
; R1 = 2*floor(sqrt(D))
; U = floor(sqrt(D)) + Pi+1
; U1 = floor(sqrt(D)) + Pi
; V = Qi+1
; V1 = Qi
; P = Ai mod N
; P1 = Ai-1 mod N
; A = qi
; S = (-1)^(i+1)
)
#'(lambda ()
(catch 'tryfactoring5
(do ((it 200) h1) ; Iterationenzahl und Hilfsvariable
((<= it 0))
(when Anfangszustand ; muß initialisieren
(calc-factorbase D) ; Faktorbasis berechnen.
(dotimes (i m) ; Test auf Teilbarkeit in Faktorbasis.
(if (zerop (mod n (aref factorbase i)))
; muß ein echter Teiler sein, weil n nicht prim
(throw 'tryfactoring5 (aref factorbase i))
) )
(setq R (isqrt D))
(setq R1 (* 2 R))
(setq U R1)
(setq U1 R)
(setq V (- D (* R R)))
(if (zerop V) ; k*N=D ist Quadratzahl = R^2
; -> g:=ggT(R,N) teilt N.
; Bei g=1 wäre ggT(R^2,N)=1 => R^2|k => N=1, Widerspruch.
; Bei g=N wäre N|R => N^2|R^2=k*N => N|k, das passiert
; zu unseren Lebzeiten nicht mehr.
(throw 'tryfactoring5 (gcd R n)) )
(setq V1 1)
(setq P R)
(setq P1 1)
(setq A R)
(setq S -1)
(setq Anfangszustand nil)
(decf it 2)
)
; Hier gelten die ganzen Invarianten.
; (assert (zerop (mod (+ (* P1 P1) (* S V1)) n)))
; (assert (zerop (mod (- (* P P) (* S V)) n)))
(if (setq h1 (base-factor (* S V)))
(add-eqn (cons (car h1) (cons P (cdr h1)))) )
(when (= V 1) ; Periode des Kettenbruches erreicht ->
(incf D n)
(setq Anfangszustand t) ; neuer Anlauf mit größerem k.
)
; nächste Kettenbruchiteration:
(multiple-value-setq (A h1) (floor U V)) ; A:=qi+1
(psetq U1 U U (- R1 h1)) ; U1:=R+Pi+1, U:=R+Pi+2
(psetq V1 V V (- V1 (* A (- U U1)))) ; V1:=Qi+1, V:=Qi+2
(setq S (- S)) ; S:=(-1)^(i+2)
(psetq P1 P P (mod (+ (* A P) P1) n))
; P1:=Ai mod N, P:=Ai+1 mod N
; jetzt ist ein Durchlauf fertig, i:=i+1
(decf it)
) ) ) ) ) ) )
#|
; sechster Faktorisierungsalgorithmus: Quadratisches Sieben.
|#
; (cyclic-list x1 ... xn) mit n>0
; liefert die zyklische Liste #1=(x1 ... xn . #1#)
(defun cyclic-list (&rest args)
(setf (cdr (last args)) args)
args
)
; (verffz b) verfeinert die in b steckenden Faktorzerlegung von n = (fz-num b)
; mit bis zu k Iterationsschritten (k fehlt -> beliebig viele Schritte), solange
; bis eine bestimmte Bedingung (return-test b) zutrifft ( /= NIL liefert).
; Ergebnis ist die Anzahl der verbrauchten Iterationen.
(defun verffz (b &optional (k nearly-infinite)
&key (return-test #'(lambda (x)
(declare (ignore x))
nil) )
&aux (it 0))
(if (eq (fz-cl b) 'prime) (return-from verffz it)) ; nichts zu tun.
(if (eq (fz-cl b) 'probprime)
(let ((n (fz-num b)))
; Jetzt muß der Primzahlverdacht erhärtet werden.
; Daß n>2 ungerade ist, ist schon bekannt.
(if (null (fz-other b)) ; noch keine wartenden Prozeduren
(setf (fz-other b)
(cyclic-list
(tryprimeprover1 n)
(tryprimeprover2 n)
; usw. (weitere Beweisversucher)
) ) )
; Jetzt müssen die wartenden Funktionen der Reihe nach aktiviert
; werden, bis sie ein Ergebnis liefern.
(incf it (tryprimeprove b k))
(return-from verffz it)
) )
(if (eq (fz-cl b) 'notprime)
; erst einmal muß ein Faktor von n gefunden werden:
(let ((n (fz-num b)))
(if (null (fz-other b))
(setf (fz-other b)
(cyclic-list
(tryfactor1 n)
(tryfactor2 n)
(tryfactor3 n)
; usw.
) ) )
(incf it (tryfactor b k))
) )
(if (eq (fz-cl b) 'notprime)
; konnte noch nicht faktorisieren
(return-from verffz it))
(assert (eq (fz-cl b) 'factored))
(if (fzl-recalc (fz-other b))
(return-from verffz it)) ; Verfeinern unmöglich
; Jetzt wird die Anstrengung auf die einzelnen Faktoren verteilt.
(let ((l nil) f) ; l = Liste noch zu verfeinernder Faktoren
(loop
(when (or (>= it k) (funcall return-test b)) (return))
(if (null l) (setq l (fzl-factorlist (fz-other b)))) ; von neuem
(setq f (first l))
(if (not (integerp f))
(progn
(incf it (verffz f ; rekursiv verfeinern
(ceiling (max 0 (- k it)) (length l))))
(when (eq (fz-cl f) 'factored)
; in b wird f durch seine Faktoren ersetzt
(setf (fzl-factorlist (fz-other b))
(mergefz (remove f (fzl-factorlist (fz-other b))
:count 1 :test #'eq)
(copy-list (fzl-factorlist (fz-other f)))
) ) )
; (beachte: l bleibt hierbei die Liste der noch nicht
; abgearbeiteten Faktoren, der Schwanz von
; (fzl-factorlist (fz-other b)). )
(when (member (fz-cl f) '(prime factored))
(if (fzl-recalc (fz-other b)) ; noch was zu tun?
(return-from verffz it))
)
) )
(pop l)
)
; (fzl-factorlist (fz-other b)) ist jetzt zur Genüge verfeinert.
it
) )
; (pfz n) liefert die Primfaktorzerlegung von n>0:
; alle Primfaktoren, in aufsteigender Reihenfolge, evtl. mehrfach vertreten.
(defun pfz (n)
(if (< n *Kleinheit*)
(klpfz n)
(let ((b (pfz-table-lookup n)))
(when (null b) ; n noch nicht in der Tabelle?
(mr n) ; n in die Tabelle eintragen
(setq b (pfz-table-lookup n))
)
(do ((i 5 (* 2 i))) ; i wächst stark
((or (eq (fz-cl b) 'prime)
(and (eq (fz-cl b) 'factored) (fzl-allprimes (fz-other b)))
))
(verffz b i) ; ewig verfeinern
)
(if (eq (fz-cl b) 'prime)
(list n)
(mapcar #'fz-key (fzl-factorlist (fz-other b)))
) ) ) )
; (setpfz n l) speichert in der Tabelle ein (von irgendwoher bekanntes)
; Primfaktorzerlegungsergebnis ab. Es sollte irgendwann l als Ergebnis von
; (pfz n) gekommen sein.
; Nach (setpfz n l) ist sichergestellt, daß in Zukunft bei (pfz n) ohne
; Rechnung sofort das Ergebnis l kommt.
(defun setpfz (n l)
(if ; Plausibilitätsprüfung, ob (pfz n) = l sein kann.
(and (apply #'<= 2 l)
(every #'(lambda (f) (or (>= f *Kleinheit*) (klprimtest f))) l)
(= n (apply #'* l))
)
(progn
; erst die Primfaktoren als Primzahlen in die Tabelle abspeichern
(setq l
(mapcar
#'(lambda (f)
(if (< f *Kleinheit*)
f
(let ((b (pfz-table-lookup-insert f)))
(unless (and (eq (fz-cl b) 'prime)
(integerp (fz-other b)))
(setf (fz-cl b) 'prime)
(setf (fz-other b) nil)
)
b
) ) )
l
) )
; l ist die neue Faktorzerlegungsliste
(when (and (>= n *Kleinheit*) (> (length l) 1))
(let ((b (pfz-table-lookup-insert n)))
(setf (fz-cl b) 'factored)
(setf (fz-other b) (make-fzl :allprimes t :factorlist l))
) )
n ; als Ergebnis
)
(error "Fehler: ~D hat nicht die Primfaktorzerlegung ~S.~%" n l)
) )
;-------------------------------------------------------------------------------
; Anwendungen (benutzen nur pfz):
; (pfzv n) ergibt die Primfaktorzerlegung von n>0 in der Gestalt
; ((p1 . e1) (p2 . e2) ... (pk . ek)) mit Primzahlen p1<...<pk und
; Exponenten e1,...,ek>0 mit n=p1^e1 ... pk^ek.
(defun pfzv (n)
(do ((l1 (pfz n))
(l2 nil))
((endp l1) (reverse l2))
(let* ((p (first l1))
(e (do ((i 0 (1+ i)))
((or (null l1) (not (= p (first l1)))) i)
(pop l1)
)) )
(push (cons p e) l2)
) ) )
; (primteiler n) liefert die Primteiler von n>0, jeden nur einmal.
(defun primteiler (n) (mapcar #'car (pfzv n)))
; (pdivisors n) ergibt die positiven Teiler einer Zahl n>0, in
; irgendeiner Reihenfolge
(defun pdivisors (n)
(reduce
#'(lambda (l pe) ; ergibt l, elementweise mit 1,p,...,p^e
(let ((p (first pe)) ; multipliziert.
(e (rest pe)))
(mapcan
#'(lambda (m) ; ergibt die mit m multiplizierte Liste l
(mapcar #'(lambda (x) (* m x)) l)
)
(mapcar #'(lambda (i) (expt p i)) (intlist 0 e))
) ) )
(pfzv n)
:initial-value '(1)
)
)
; (divisors n) ergibt die ganzzahligen Teiler einer Zahl n /= 0
(defun divisors (n)
(let ((pdiv (pdivisors (abs n))))
(append pdiv (mapcar #'- pdiv))
) )
; (euler-phi n) ergibt für n>0 die Eulersche phi-Funktion von n,
; d.h. die Anzahl der zu n teilerfremden natürlichen Zahlen >0, <=n.
(defun euler-phi (n)
(reduce #'*
(mapcar #'(lambda (pe)
(let ((p (car pe)) ; Primzahl
(e (cdr pe)) ; Exponent, >0
)
(* (expt p (1- e)) (1- p))
) )
(pfzv n)
) ) )
; (prim-von-bis a b) ergibt für eine Liste aller Primzahlen von a bis b
; (inklusive).
(defun prim-von-bis (a b)
(remove-if-not #'isprime (intlist a b))
)