Primzahltest nach Miller-Rabin

Fragen zu mathematischen Problemen
Antworten
Benutzeravatar
Dirty Oerti
Beiträge: 2229
Registriert: Di Jul 08, 2008 5:05 pm
Wohnort: Thurndorf / Würzburg

Primzahltest nach Miller-Rabin

Beitrag von Dirty Oerti » Mo Jan 04, 2010 12:50 pm

Tag zusammen :)

Ich brauche einen Primzahltest, der möglichst schnell und effizient (und sicher) testet, ob eine (seeehr große) Zahl eine Primzahl ist.
Zur Vorgeschichte:
Eigentlich wollte ich das Ganze über das Sieb des Eratosthenes machen.. sprich alle Primzahlen bis zur Wurzel der Zahl berechnen und dann auf Teilbarkeit prüfen.
Problem ist folgendes:
Bis zur Größenordnung 10^300 gibt es etwa 10^294 Primzahlen. Angenommen die sind etwa gleich verteilt muss ich also bis zur Wurzel einer Zahl der Größe 10^300 eine Anzahl an 10^147 Primzahlen berechnen.
Wenn ich das Ganze auf einem 32bit Computer mache mit 4 Byte Zeiger (auf Datenstrukturen, die dann die jeweiligen Primzahlen enthalten), dann verbrauche ich allein für die Zeiger 4 * 10^147 Byte Speicher, was echt nicht geht^^

Deswegen versuche ich es nun mit dem sog. Miller-Rabin-Test.
Der liefert zwar nicht mit 100% Sicherheit Primzahlen, allerdings beschränkt sich der Fehler auf (1/4)^k bei k Durchläufen, was akzeptabel ist.

Nun hab ich das in Code umgesetzt und es scheint auch zu funktionieren, jedoch bin ich mir sicher, dass man das noch optimieren kann (siehe die Geschwindigkeit von DIESEM Test bei Zahlen mit mehr als 500 Stellen)

Nur weiß ich nicht WIE.
Ich glaube es hängt damit zusammen, wie ich meine zufällig gewählten Zahlen wähle :)

(Achja: xint sind vorzeichenbehaftete ints beliebiger Größe)

Code: Alles auswählen

// Modulare Exponentation
// Berechnet also (num^exp)%n
// Ich denke auch, dass es funktioniert ;)
xint mod_exp(xint num, xint exp, xint n)
{
	xint res;
	res = 1;
	xint aktpot;
	aktpot = num;
	while ( exp > 0) {
		if (exp % 2 == 1) {
			res = res * aktpot;
		}
		aktpot = aktpot * aktpot;
		aktpot = aktpot % n;
		exp = exp / 2;
	}
	res = res % n;
	return res;
}


bool miller_rabin(xint n)
{
	xint a;
	xint t;
	xint alpha;
	xint p;
	bool pseudoprim;
// Zufällige Basis a wählen
	srand(time(NULL)+4);
	a = (rand()%123456)+2;
	while (a < n) {
	a *= (rand()%123457)+1;
	}
	while (a >= n) {
		a -= n-1;
	}
// Hier wird der Exponent berechnet
	t = n - 1;
	alpha = 0;
	while ( t%2 == 0) {
		t = t/2;
		alpha = alpha + 1;
	}
// Und nun das Ergebnis von (a^t)%n
	p = 1;
	p = mod_exp(a,t,n);
	pseudoprim = false;
	alpha = alpha - 1;

	if ((p == n-1) || (p==1)) {
		pseudoprim = true;
	}

	while ((!pseudoprim) && (alpha>0) && (p>1)) {
		p = p * p;
		p = p % n;
		alpha = alpha - 1;
		if (p == n-1) {
			pseudoprim = true;
		}
	}
	return pseudoprim;
}
Warum das so funktioniert verstehe ich (leider) auch nicht, da wäre ich froh, wenn mir das jemand erklären könnte :)
Bei Fragen einfach an daniel[ät]proggen[Punkt]org
Ich helfe gerne! :)
----------
Wenn du ein Licht am Ende des Tunnels siehst, freu dich nicht zu früh! Es könnte ein Zug sein, der auf dich zukommt!
----
It said: "Install Win95 or better ..." So I installed Linux.

AnGaiNoR
Beiträge: 212
Registriert: Sa Jul 19, 2008 7:07 pm
Wohnort: Dresden

Re: Primzahltest nach Miller-Rabin

Beitrag von AnGaiNoR » Mo Jan 04, 2010 6:39 pm

Ich wusste bis gerade eben gar nicht von der Existenz dieses Tests ^^
Bei Wikipedia gibt es einen Link auf ein meiner Meinung nach gutes PDF-Dokument, dass den Test erklärt.

Zur Code-Optimierung habe ich nur ein paar Anregungen (die allerdings auch falsch sein könnten):

An der Stelle zur Berechnung des Exponenten benutzt du eine while-Schleife. Eine while-Schleife ist aber meiner Meinung nach nur dann sinnvoll, wenn man vorher nicht weiß, wie oft sie ausgeführt werden soll. Die Schleife läuft so lange, bis t mod 2 == 0. Das heißt aber, dass in Binärdarstellung (zumindest in der mir bekannten) das letzte Bit 0 sein muss. Mit t = t / 2 erreichst du bei Ganzzahlen in der Binärdarstellung im allgemeinen eine Rechtsverschiebung. Ich würde also vorschlagen, dass du gleich das letzte 1 gesetzte Bit als Indikator nutzt um den Exponent zu ermitteln.

Ich schätze mal ähnliches kann auch bei der Basis-Ermittlung in der zweiten while-Schleife gelingen. Wenn ich mich nicht irre läuft das am Ende auf ein einfaches Modulo und einen Vergleich hinaus; da bin ich mir aber nicht so sicher.

Bei der Modularen Exponentation tritt wieder ein ähnliches Problem wie weiter oben auf: in einer while-Schleife halbierst du immer wieder einen Wert und testest ob der Wert modulo 2 gleich 1 ist, oder mit anderen Worten ob das letzte Bit gesetzt ist. Also kannst du eine Vereinfachung genau wie oben durchführen.
Physics is like sex: sure, it may give some practical result, but that's not why we do it.
(Richard P. Feynman)

Benutzeravatar
Dirty Oerti
Beiträge: 2229
Registriert: Di Jul 08, 2008 5:05 pm
Wohnort: Thurndorf / Würzburg

Re: Primzahltest nach Miller-Rabin

Beitrag von Dirty Oerti » Di Jan 05, 2010 5:11 pm

Das scheint mir einer der schnellsten und gleichzeitig sichersten und am einfachsten zu implementierenden Tests zu sein, die ich gefunden habe, deswegen Miller-Rabin :)
Das PDF-Dokument hatte ich mir noch gar nicht angesehen, das ist aber sehr gut, danke schön.

Die Modulooperationen habe ich nun schon optimieren können, nun ist eigentlich nur noch das Dividieren durch 2 optimierbar... denke ich.
Außerdem wird die Basis nun einfacher gewählt. Die Basis muss zwar im Bereich 1 < a < zuTestendeZahl liegen, dass heißt ja aber nicht, dass die Basis nicht im Bereich bis ca 10000 bewegen kann, auch wenn die Zahl 10^200 oder so etwas ist.
Bei Fragen einfach an daniel[ät]proggen[Punkt]org
Ich helfe gerne! :)
----------
Wenn du ein Licht am Ende des Tunnels siehst, freu dich nicht zu früh! Es könnte ein Zug sein, der auf dich zukommt!
----
It said: "Install Win95 or better ..." So I installed Linux.

AnGaiNoR
Beiträge: 212
Registriert: Sa Jul 19, 2008 7:07 pm
Wohnort: Dresden

Re: Primzahltest nach Miller-Rabin

Beitrag von AnGaiNoR » Mi Jan 06, 2010 7:41 pm

Code: Alles auswählen

   while ( t%2 == 0) {
      t = t/2;
      alpha = alpha + 1;
   }
Ich würde denken, dass du das etwas effektiver so hinbekommst.

Code: Alles auswählen

   while ( t & 1 ) {
      t = t >> 1;
      ++alpha;
   }
Damit hast du aber eigentlich auch nur das Modulo durch AND ersetzt (ich denke AND ist schneller?) und die Division mit einer Bitverschiebung (was eigentlich ein guter Compiler meines Wissens nach automatisch macht).
Das gilt natürlich nur, wenn der xint-Typ Bitverschiebung und AND-Operator hergibt.
Physics is like sex: sure, it may give some practical result, but that's not why we do it.
(Richard P. Feynman)

Benutzeravatar
Dirty Oerti
Beiträge: 2229
Registriert: Di Jul 08, 2008 5:05 pm
Wohnort: Thurndorf / Würzburg

Re: Primzahltest nach Miller-Rabin

Beitrag von Dirty Oerti » Mi Jan 06, 2010 8:17 pm

Tag :)

Ja, im Prinzip hab ich genau das jetzt erledigt.
Dazu musste ich natürlich erst die Bitverschiebungsoperatoren einbauen, aber jetzt funktioniert es schonmal deutlich schneller.

Ich hab auch eine neue Funktion geschrieben, die 2^x mod N schneller berechnet.
Das Hauptproblem liegt denke ich im Moment in xint und zwar in der Multiplikationsroutine. Die muss ich noch effizienter machen...

Die neue 2^x mod N Funktion:

Code: Alles auswählen

xint mod_2exp(xint exp, xint n)
{
	xint res = 1;
	xint tmp = 1;
//	std::cout << "2 hoch " << exp << std::endl;
	while (!exp.isZero()) {
		tmp = tmp << 1;
		if (exp.mod2() == 0) {
			exp = exp >> 1;
		} else {
			tmp += 1;
			exp-=1;
		}
//		std::cout << tmp << std::endl;
	}
//	std::cout << "EXPONENT fertig zerlegt" << std::endl;
//	std::cout << tmp << std::endl;
	while (!tmp.isZero()) {
		if (tmp.mod2() == 1) {
			res = res << 1;
		} else {
			res = res % n;
			res = res * res;
		}
		tmp = tmp >> 1;
	}
	res = res >> 1;
	res = res % n;
	return res;
}
Ein Beispiel, um zu veranschaulichen, wie die Funktion läuft:
(Das Modulo lass ich mal weg, eigentlich ist die Funktion davon ja klar. Es muss aber übrigens GENAU da und NUR da hin wo es jetzt ist, sonst geht es nicht. Warum? - Keine Ahnung)

2^7

Das könnte man auch schreiben als:

2 * 2^6

oder als

2 * (2^3)^2

oder als

2 * (2 * 2^2)^2

Und genau diese Aufspaltung macht die Funktion in der oberen Schleife.
In der unteren werden dann die jeweiligen Aktionen ( mal 2 oder quadrieren) angewandt.
Was zu tun ist, dass wird in tmp gespeichert (bit für bit)
Da führende 0 bits evtl gelöscht werden könnten beginnt tmp mit einer führenden 1, deren verursachter Fehler dann am Ende (nach der Schleife) durch ein /2 wieder gut gemacht wird.
Bei Fragen einfach an daniel[ät]proggen[Punkt]org
Ich helfe gerne! :)
----------
Wenn du ein Licht am Ende des Tunnels siehst, freu dich nicht zu früh! Es könnte ein Zug sein, der auf dich zukommt!
----
It said: "Install Win95 or better ..." So I installed Linux.

Antworten