Entwickler-Ecke
Algorithmen, Optimierung und Assembler - Primfaktorzerlegung optimieren
Born-to-Frag - So 20.11.05 14:00
Titel: Primfaktorzerlegung optimieren
Ich überlege seit längerer Zeit wie ich meine Primfaktorzerlegung optimieren kann.. Ich habe schon mehrere ansätze gesehen und auch schon ein paar sachen verbessert. Nur bei zahlen wie 9223372036854775805 wird es dann schon schwieriger, weil sie sehr große Primfaktoren hat.
Also hier mal der Sourcecode:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:
| function IsPrimzahl(Zahl: Int64): Boolean; begin Result := False; Teiler := 2; while Teiler <= Trunc(Sqrt(1.0 * Zahl)) do begin if Zahl mod Teiler = 0 then Exit else Inc(Teiler); end; Result := True; end;
function PrimFaktorZerlegung (Zahl: Int64): String; begin Teiler := 2; Result := ''; while Zahl > 1 do begin if (Teiler > 2) and (Teiler mod 2 = 0) then Inc(Teiler); if IsPrimzahl(Zahl) = True then Teiler := Zahl; while (Zahl > 1) and (Zahl mod Teiler = 0) do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; end; Inc(Teiler); end; SetLength(Result, Length(Result) - 3); end; |
Und OnClick:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29:
| procedure TForm1.B_BerechneClick(Sender: TObject); begin Z_Begin := GetTickCount; Prim := 'Die Zahl setzt sich aus folgenden Primfaktoren zusammen: ';
try Zahl := StrToInt64(E_Ausgabe.Text); except on EConvertError do begin Messagebox(Handle, 'Eingabefehler - Zahl zu groß/klein oder ungültige Zeichen!', 'Fehler', MB_OK or 16); E_Ausgabe.SetFocus; Exit; end; end;
if Zahl < 2 then begin Messagebox(Handle, 'Eingabefehler - Zahl muss größer als eins sein.', 'Fehler', MB_OK or 16); Exit; end;
if IsPrimzahl(Zahl) = False then Prim := Prim + PrimFaktorZerlegung(Zahl);
Z_Ende := GetTickCount;
Memo1.Lines.Add(Prim); Memo1.Lines.Add('Diese Berechnung dauerte ' + FloatToStr(Z_Ende - Z_Begin) + 'ms!'); Memo1.Lines.Add('');
E_Ausgabe.SetFocus; end; |
Vielleicht habt ihr ja noch idden.. (Achso, und ich hab den Thread optimierung einer Primzahlfunktion schon gesehen ;))
greetz
EDIT: Bisschen was geändert ;)
AXMD - So 20.11.05 15:09
Ich würde an deiner Stelle die Faktoren in einem Array of Integer speichern - das ist bestimmt um einiges schneller als der String mit dem IntToStr-Zeug.
AXMD
Allesquarks - So 20.11.05 15:18
Also ersteinmal würde ich immer um zwei hochzählen und den Teiler zwei, den du dann ja nicht drinhast patchen. Desweiteren würde ich deiner Funktion Isprime deine Variable Teiler übergeben, da aufgrund deines Algorithmus keine Teiler kleiner als deine Variable Teiler möglich sind. Also müssen diese bei Isprime auch nicht überprüft werden. Macht sich besonders bei großen Primzahlen gut.
Born-to-Frag - So 20.11.05 17:20
JA, aber IntToStr wird eh nur benutzt wenn es ein Primfaktor ist, wenn ich aber so richtig hohe zahlen habe dann dauert das schon mal 2min.. das liegt aber nicht daran. Das mit dem um 2 erhöhen bau ich jetzt gleich noch ein
ich meld mich dann noch einmal
greetz und thx
AXMD - So 20.11.05 17:22
Born-to-Frag hat folgendes geschrieben: |
JA, aber IntToStr wird eh nur benutzt wenn es ein Primfaktor ist |
Kostet trotzdem unnötige Zeit ;)
AXMD
Born-to-Frag - So 20.11.05 17:54
Ok, also ich hab eure vorschläge jetzt mal mit eingebracht und ich komm bei der zerlegung von 9223372036854775806 auf über 2min.. geht das noch schneller? :)
greetz
AXMD - So 20.11.05 18:06
Vielleicht postest du hier vorher erstmal deinen neuen Code ;)
AXMD
Born-to-Frag - So 20.11.05 19:10
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39:
| function IsPrimzahl(Zahl: Int64): Boolean; begin Result := False; Teiler := 3; if Zahl mod 2 = 0 then Exit; while Teiler <= Trunc(Sqrt(1.0 * Zahl)) do begin if Zahl mod Teiler = 0 then begin Teiler := 3; Exit; end else Inc(Teiler, 2); end; Result := True; Teiler := 3; end;
function PrimFaktorZerlegung (Zahl: Int64): String; begin Teiler := 3; Result := ''; repeat if Zahl mod 2 = 0 then begin Result := Result + IntToStr(2) + ' * '; Zahl := Zahl div 2; if IsPrimzahl(Zahl) = True then Teiler := Zahl; end; until Zahl mod 2 > 0;
while Zahl > 1 do begin
while (Zahl > 1) and (Zahl mod Teiler = 0) do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; if IsPrimzahl(Zahl) = True then Teiler := Zahl; end; Inc(Teiler, 2); end; SetLength(Result, Length(Result) - 3); end; |
Mit Array gehts eigendlich nicht schneller.. ok.. vielleicht 0.2Sekunden insgesammt. Aber gibt es keine andere möglichkeit das noch richtig zu verschnellern?
Greetz
EDIT: Funktion Update - Ich teste auch grad noch eine Zahl dann sag ich nochmal wie lange es gedauert hat :)
Allesquarks - So 20.11.05 19:47
Dein Algorithmus kocht ja gewissermaßen deine Zahl runter:
nehmen wir an zahl x=2*2*2*3*7*7*13*13. Rechne ich jetzt nicht aus!
Wenn dein Alfo einen Primfaktor findet notiert er ihn und teilt anschließend Zahl x durch diesen Primteiler. Wenn du alle Teiler bis! 5 durchgegangen bist sieht die Restzahl also wie folgt aus: 7*7*13*13. Wenn du jetzt Teiler von 5 auf 7 erhöhst muss dein IsPrimzahl doch nicht noch einmal 2,3 und 5 überprüfen.
Einmal (in deinem Fall) kommt pro erhöhung auf n ca. hab jetzt nicht genau gekuckt n/2 Divisionen in IsPrime hinzu also nach Gauß: 1+2+3+4+5+6...=n*(n+1)/2 folgt in deinem Fall, da du ja jetzt nur noch die ungeraden überprüfst:
Divisionen nach Teiler n : n*(n+1)/4 im Gegensatz zu n/2 für den anderen Fall. Einmal also quadratisch und einmal linear!!!
Born-to-Frag - So 20.11.05 20:02
Allesquarks hat folgendes geschrieben: |
Dein Algorithmus kocht ja gewissermaßen deine Zahl runter:
nehmen wir an zahl x=2*2*2*3*7*7*13*13. Rechne ich jetzt nicht aus!
Wenn dein Alfo einen Primfaktor findet notiert er ihn und teilt anschließend Zahl x durch diesen Primteiler. Wenn du alle Teiler bis! 5 durchgegangen bist sieht die Restzahl also wie folgt aus: 7*7*13*13. Wenn du jetzt Teiler von 5 auf 7 erhöhst muss dein IsPrimzahl doch nicht noch einmal 2,3 und 5 überprüfen. |
Das er nach dem erhöhen von dem Teiler wieder auf Primzahl überprüft war ein Fehler von mir, hab ich behoben ( Hab ich in der Funktion ober verbessert - er überprüft nur noch auf primzahl nachdem er geteilt hat. Was z.B. seh sinnvoll hier ist: 746782658 er teilt die Zahl durch 2, überprüft dann 373391329 ob es eine Primzahl ist und stellt fest: ja es ist eine! )
Allesquarks hat folgendes geschrieben: |
Einmal (in deinem Fall) kommt pro erhöhung auf n ca. hab jetzt nicht genau gekuckt n/2 Divisionen in IsPrime hinzu also nach Gauß: 1+2+3+4+5+6...=n*(n+1)/2 folgt in deinem Fall, da du ja jetzt nur noch die ungeraden überprüfst:
Divisionen nach Teiler n : n*(n+1)/4 im Gegensatz zu n/2 für den anderen Fall. Einmal also quadratisch und einmal linear!!! |
Wie meinst du das? Kannst du mir das mal an eimen Beispiel zeigen :oops:
Thx 4 help
greetz
Allesquarks - So 20.11.05 20:19
Deine Funktion IsPrimzahl funktioniert eigentlich einwandfrei und ist richtig und ist für eine Primzahlüberprüfung genau das richtige. Allerdings nicht in deinem Kontext:
Wie ich oben schon (vielleicht etwas kurz) probiert hab zu zeigen:
Stell dir vor du hast die Zahl x=2*2*2*3*7*7*13*13=bla allerdings ausgerechnet eingegeben bekommen. Dein Programm fängt jetzt an und zwar (gepatcht die zwei) und anschließend die drei. Nun ist die Modulodivision durch 3 Null und du teilst durch drei oder gegebenenfalls mehrmals da das ja auch eine Schleife ist. Sobald diese Schleife terminiert wird die Restzahl nie wieder durch drei teilbar sein genauso ist sie auch nicht mehr durch zwei teilbar. Wenn du jetzt mod 5 abgearbeitet hast gilt gleiches für die fünf. In IsPrimzahl musst du also diese ganzen Zahlen, die du ja schon abgearbeitet hast nicht mehr überprüfen.
Aber meine Rechnung ist ebenfalls fehlerhaft (hatte einen Codeschnipsel übersehen) Meiner meinung kannst du durch diese Verbesserung n*(n+1)/4 Überprüfungen durch n/2 ersetzen allerdings von insgesamt n! in deinem IsPrimzahl (hoffe das stimmt jetzt).
//Edit:
Stell dir ein Primzahlpaar vor da sparst Du bei größeren Zahlen praktisch die Hälfte der Operationen.
Ich persönlich würd IsPrimzahl sowieso in den Mülleimer kloppen, da du in deiner Hauptfunktion sobald Teiler>= Zahl ist, dass Zahl=Primzahl ist
Born-to-Frag - So 20.11.05 20:35
Allesquarks hat folgendes geschrieben: |
Aber meine Rechnung ist ebenfalls fehlerhaft (hatte einen Codeschnipsel übersehen) Meiner meinung kannst du durch diese Verbesserung n*(n+1)/4 Überprüfungen durch n/2 ersetzen allerdings von insgesamt n! in deinem IsPrimzahl (hoffe das stimmt jetzt). |
Wo kann ich das ersetzen. Sorry, aber ich hab grad ein Brett vorm Kopf. Wo hab ich denn n*(n+1)/4 Überpfüfungen und wie kann ich diese durch n/2 ersetzen?
Schreib mir doch mal bitte die function.
Ich hab jetzt grad die Zahl 9223372036854775806 zerlegen lassen.. 403 Sekunden. Sehr hohe Primfaktoren...
greetz
Allesquarks - So 20.11.05 20:43
//hab oben noch was ersetzt
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:
| function PrimFaktorZerlegung (Zahl: Int64): String; begin Teiler := 3; Result := ''; repeat if Zahl mod 2 = 0 then begin Result := Result + IntToStr(2) + ' * '; Zahl := Zahl div 2; if IsPrimzahl(Zahl) = True then Teiler := Zahl; end; until Zahl mod 2 > 0;
while (Teiler<Zahl)AND(Zahl > 1) do begin
while (Teiler<Zahl) AND (Zahl mod Teiler = 0)do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; end; Inc(Teiler, 2); end; if not(zahl=1) then begin end; SetLength(Result, Length(Result) - 3); end; |
hab ich nicht getestet aber so würd ich das machen
//jetzt hatt ich aber nen Brett vorm Kopf
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:
| function PrimFaktorZerlegung (Zahl: Int64): String; begin Teiler := 3; Result := ''; repeat if Zahl mod 2 = 0 then begin Result := Result + IntToStr(2) + ' * '; Zahl := Zahl div 2; if IsPrimzahl(Zahl) = True then Teiler := Zahl; end; until Zahl mod 2 > 0;
if zahl >1 then begin while (Teiler<=Zahl) do begin
while (Teiler<=Zahl) AND (Zahl mod Teiler = 0)do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; end; Inc(Teiler, 2); end; end; SetLength(Result, Length(Result) - 3); end; |
Born-to-Frag - So 20.11.05 21:17
Allesquarks hat folgendes geschrieben: |
//hab oben noch was ersetzt
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:
| function PrimFaktorZerlegung (Zahl: Int64): String; begin Teiler := 3; Result := ''; repeat if Zahl mod 2 = 0 then begin Result := Result + IntToStr(2) + ' * '; Zahl := Zahl div 2; if IsPrimzahl(Zahl) = True then Teiler := Zahl; end; until Zahl mod 2 > 0;
while (Teiler<Zahl)AND(Zahl > 1) do begin
while (Teiler<Zahl) AND (Zahl mod Teiler = 0)do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; end; Inc(Teiler, 2); end; if not(zahl=1) then begin end; SetLength(Result, Length(Result) - 3); end; |
hab ich nicht getestet aber so würd ich das machen
//jetzt hatt ich aber nen Brett vorm Kopf
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:
| function PrimFaktorZerlegung (Zahl: Int64): String; begin Teiler := 3; Result := ''; repeat if Zahl mod 2 = 0 then begin Result := Result + IntToStr(2) + ' * '; Zahl := Zahl div 2; if IsPrimzahl(Zahl) = True then Teiler := Zahl; end; until Zahl mod 2 > 0;
if zahl >1 then begin while (Teiler<=Zahl) do begin
while (Teiler<=Zahl) AND (Zahl mod Teiler = 0)do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; end; Inc(Teiler, 2); end; end; SetLength(Result, Length(Result) - 3); end; | |
Da glaube ich aber das meine funktion wesentlich schneller ist! Dies hat folgenden grund:
IsPrimzahl ist nicht unnötig, denn nehmen wir doch mal an das die Zahl aus folgenden Faktoren besteht: 2*3*2147483647. Dann überprüft er nach dem Teilen durch 3 nicht mehr ob 2147483647 eine Primzahl ist.
das
while (Teiler<=Zahl) AND (Zahl mod Teiler = 0) wird bestimmt keinen unterschied machen, aber ich werds nachher noch mal testen, Bin jetzt erts mal 15min weg.
greetz
Allesquarks - So 20.11.05 21:23
Zitat: |
Da glaube ich aber das meine funktion wesentlich schneller ist! Dies hat folgenden grund:
IsPrimzahl ist nicht unnötig, denn nehmen wir doch mal an das die Zahl aus folgenden Faktoren besteht: 2*3*2147483647. Dann überprüft er nach dem Teilen durch 3 nicht mehr ob 2147483647 eine Primzahl ist. |
Da würd ich grad dagengenhalten.
Wenn ich nach der drei Deine Funktion ISPrimzahl aufrufe und der Compi dann dort alle ungeraden Zahlen bis 2147483647 durchgehe, oder dies in der while- Schleife im Hauptprogramm macht nur insofern einen Unterschied, da man dann gerade vieles doppelt macht aber probier es aus
Born-to-Frag - So 20.11.05 21:35
Du weißt das er bei IsPrimzahl nur bis zu Wurzel prüft? (also ca 46000).. aber ich probiers jetzt aus und schreibs dann hier als edit rein...
greetz
Allesquarks - So 20.11.05 21:39
Das mit SQRT kommt noch hinzu allerdings kannste ddas auch in meinen Vorschlag einbringen indem die Abfragen in den Whiles auch nur bis SQRT(Zahl laufen)
Born-to-Frag - So 20.11.05 21:42
Ich weiß nicht genau was du jetzt meinst, aber ich kann es nicht in die PrimFaktorZerlegung-funktion einbringen, denn 22 besteht z.B. aus den Faktoren 2 und 11, und die Wurzel aus 22 ist ca. 4,69 ;)
greetz
PS: Teste grade deine Funktion mit der Zahl 9223372036854775806. Meine Funktion hat 403 Sekunden gebraucht.
EDIT: WOW!! 221Sekunden. Nicht schlecht!! Hätte ich echt nicht gedacht.. Aber warum? Ich muss mir das nochmal ansehen..
EDIT2: Na gut, es ist je nach zahl verschieden.. wenn ich zu oft nach Primzahl überprüfe spart es irgendwann keine Zeit mehr ein / frisst Zeit
EDIT3: Da sieht man wieder das Problem: bei der Zahl 12884901882 (2*3*2147483647) braucht deine Funktion 36Sekunden, meine 110ms :D
Allesquarks - So 20.11.05 21:49
Meiner Meinung nach könnte man das so einbringen:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32:
| function PrimFaktorZerlegung (Zahl: Int64): String; begin Teiler := 3; Result := ''; repeat if Zahl mod 2 = 0 then begin Result := Result + IntToStr(2) + ' * '; Zahl := Zahl div 2; if IsPrimzahl(Zahl) = True then Teiler := Zahl; end; until Zahl mod 2 > 0;
if zahl >1 then begin while (Teiler<=trunc(SQRT(Zahl))) do begin
while (Teiler<=trunc(SQRT(Zahl)) AND (Zahl mod Teiler = 0)do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; end; Inc(Teiler, 2); end; end; if not(zahl=1) then begin end; SetLength(Result, Length(Result) - 3); end; |
Born-to-Frag - So 20.11.05 22:10
Ok, also so ist es jetzt wirklich richtig super: Bei 12884901882 zeigt er die Faktoren auch sofort an, und für 9223372036854775806 braucht er 227Sekunden ( keine verbesserung, aber immer noch besser als meine 403 ;) )
greetz
EDIT: Man könnte noch die Tatsache nutzen, dass alle Primzahlen > 3 mod 6 = 1 oder 5 ergeben.. Denk ich mir bis morgen noch mal was aus
Allesquarks - So 20.11.05 22:14
Dann haben wir doch die Vorteile beider Methoden verbunden. Ich werd jetzt wohl aufhören. Ansonsten gabs noch einen x-Seiten langen Thread zur Primzahlfunktion. Da gibt es vielleicht noch weitergehende Anregungen.
Born-to-Frag - So 20.11.05 22:18
Allesquarks hat folgendes geschrieben: |
Dann haben wir doch die Vorteile beider Methoden verbunden. Ich werd jetzt wohl aufhören. Ansonsten gabs noch einen x-Seiten langen Thread zur Primzahlfunktion. Da gibt es vielleicht noch weitergehende Anregungen. |
Ja beide Vorteile verbunden :beer:
Den Thread hab ich mir schon Teils angesehen, kann ich vielleicht noch die IsPrimzahl-Funktion optimieren.
Danke nochmal!!
greetz
BenBE - So 20.11.05 23:52
Da geht sicherlich noch was:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36:
| function PrimFaktorZerlegung (Zahl: Int64): String; var Root: Integer; begin Result := '';
While Zahl and 1 = 0 do Begin Result := Result + IntToStr(2) + ' * '; Zahl := Zahl shr 1; end;
if zahl >= 2 then Begin Root := Trunc(SQRT(Zahl)); Teiler := 3;
while Teiler <= Root do begin while Zahl mod Teiler = 0 do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; end; Root := Trunc(SQRT(Zahl)); Inc(Teiler, 2); end; end;
if zahl <> 1 then begin Raise Exception.Create('Kann nicht sein!!!'); end; Delete(Result, Length(Result) - 2, 3); end; |
Ungetestet, ob er wirklich schneller ist ... Müsste aber ...
Born-to-Frag - Mo 21.11.05 00:07
BenBE hat folgendes geschrieben: |
Da geht sicherlich noch was:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36:
| function PrimFaktorZerlegung (Zahl: Int64): String; var Root: Integer; begin Result := '';
While Zahl and 1 = 0 do Begin Result := Result + IntToStr(2) + ' * '; Zahl := Zahl shr 1; end;
if zahl >= 2 then Begin Root := Trunc(SQRT(Zahl)); Teiler := 3;
while Teiler <= Root do begin while Zahl mod Teiler = 0 do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; end; Root := Trunc(SQRT(Zahl)); Inc(Teiler, 2); end; end;
if zahl <> 1 then begin Raise Exception.Create('Kann nicht sein!!!'); end; Delete(Result, Length(Result) - 2, 3); end; |
Ungetestet, ob er wirklich schneller ist ... Müsste aber ... |
Also Root darf nicht Integer sein, sondern Int64.
Aber der Code ist auch nicht schneller.. ca 300 Sekunden und dann die Meldung: Kann nicht sein!! bei der Zahl 9223372036854775806 :lol: :(
Dann noch was: Ich könnte doch auch die Überprüfung auf Primzahl nach dem durch 2 teilen weglassen.. manchmal sinnvoll, manchmal auch nicht.
Allesquarks - Mo 21.11.05 00:13
Zitat: |
Delphi-Quelltext 1: 2: 3: 4: 5:
| if zahl <> 1 then begin Raise Exception.Create('Kann nicht sein!!!'); end; | |
Das geht glaube ich gerade schon. Primzahlen werden dadurch erkannt, dass ein Teiler größer als SQRT(Zahl) nicht möglich ist. Dann wird die While Schleife aber abgebrochen und nicht div ausgeführt, weshalb der letzte Primfaktor in Zahl stehen kann.
Born-to-Frag - Mo 21.11.05 00:23
Außer
Delphi-Quelltext
1: 2: 3: 4: 5:
| While Zahl and 1 = 0 do Begin Result := Result + IntToStr(2) + ' * '; Zahl := Zahl shr 1; end; |
sehe ich dort keinen unterschied (ausgenommen Raise Exceprion das aber falsch war ;))
BenBE - Mo 21.11.05 01:51
:oops: Jup. Stimmt, hab's auch noch gesehen ... Hab aber nochmal ein wenig optimiert:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53:
| function PrimFaktorZerlegung (Zahl: Int64): String; var Root: DWORD; Teiler : Int64; begin Result := '';
While Zahl and 1 = 0 do Begin Result := Result + IntToStr(2) + ' * '; Zahl := Zahl shr 1; end;
if zahl >= 2 then Begin Teiler := 3;
Root := Trunc(SQRT(1.0 * Zahl)); while Zahl mod Teiler = 0 do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; end; Root := Trunc(SQRT(1.0 * Zahl)); Inc(Teiler, 2);
while Teiler <= Root do begin while Zahl mod Teiler = 0 do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; end; Root := Trunc(SQRT(1.0 * Zahl)); Inc(Teiler, 2);
while Zahl mod Teiler = 0 do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; end; Root := Trunc(SQRT(1.0 * Zahl)); Inc(Teiler, 4); end; end;
if zahl <> 1 then Result := Result + IntToStr(Zahl) + ' * ';
Delete(Result, Length(Result) - 2, 3); end; |
Liefert bei Mir Ergebnisse in ~163 Sekunden. Was dann damit schneller wäre.
@Root: Maximal DWord, da Sqrt(2^63) = 2^31 * Sqrt(2) < 2^32 --> DWORD. Aber stimmt, müsste man eigentlich.
alzaimar - Mo 21.11.05 09:14
Hab mir nicht Alles durchgelesen, aber wäre ein Primzahlentest des Teilers mit negaH's IsPrime-Kanone nicht noch schneller? Der Code ist hier irgendwo im Forum.
Born-to-Frag - Mo 21.11.05 11:14
BenBE hat folgendes geschrieben: |
:oops: Jup. Stimmt, hab's auch noch gesehen ... Hab aber nochmal ein wenig optimiert:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53:
| function PrimFaktorZerlegung (Zahl: Int64): String; var Root: DWORD; Teiler : Int64; begin Result := '';
While Zahl and 1 = 0 do Begin Result := Result + IntToStr(2) + ' * '; Zahl := Zahl shr 1; end;
if zahl >= 2 then Begin Teiler := 3;
Root := Trunc(SQRT(1.0 * Zahl)); while Zahl mod Teiler = 0 do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; end; Root := Trunc(SQRT(1.0 * Zahl)); Inc(Teiler, 2);
while Teiler <= Root do begin while Zahl mod Teiler = 0 do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; end; Root := Trunc(SQRT(1.0 * Zahl)); Inc(Teiler, 2);
while Zahl mod Teiler = 0 do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; end; Root := Trunc(SQRT(1.0 * Zahl)); Inc(Teiler, 4); end; end;
if zahl <> 1 then Result := Result + IntToStr(Zahl) + ' * ';
Delete(Result, Length(Result) - 2, 3); end; |
Liefert bei Mir Ergebnisse in ~163 Sekunden. Was dann damit schneller wäre.
@Root: Maximal DWord, da Sqrt(2^63) = 2^31 * Sqrt(2) < 2^32 --> DWORD. Aber stimmt, müsste man eigentlich. |
Ok damit brauch ich jetzt nur noch 140Sekunden.. Noch ein paar fragen dazu:
Warum nicht gleich
while Teiler <= Trunc(Sqrt(1.0 Zahl))?
Warum
shr und
While Zahl and 1 = 0?
Warum reicht es, die Zahl immer im 2 und dann um 4 zu erhöhen?
greetz
EDIT: Ich hab da mal was gehighlighted wovon ich glaube das es doppelt ist..
EDIT2: Ok hab den Sinn erkannt :oops:
alzaimar - Mo 21.11.05 12:15
Born-to-Frag hat folgendes geschrieben: |
Warum reicht es, die Zahl immer im 2 und dann um 4 zu erhöhen? |
Weil alle Primzahlen P > 3 von der Form P = 6n+/-1 (n = natürliche Zahl) sind. Warum weiss ich nicht. Ist aber so. Damit reicht es, auf diese Zahlenreihe zu testen: 2,3, 5,7, 11,13, 17,19 ...
Born-to-Frag - Mo 21.11.05 12:25
Ok ich hatte das irgendwo schon mal gelesen ( hatte ich oben ja auch schon mal erwähnt ;) ) aber war mir nicht ganz sicher.
EDIT: Ich hab mal etwas anderes an BenBes version gehighlighted, was wirklich Doppelt ist ;)
Born-to-Frag - Mo 21.11.05 13:18
alzaimar hat folgendes geschrieben: |
Hab mir nicht Alles durchgelesen, aber wäre ein Primzahlentest des Teilers mit negaH's IsPrime-Kanone nicht noch schneller? Der Code ist hier irgendwo im Forum. |
Könntest du mir mal den Link geben?
EDIT: @Alzaimar: Ich habe hier grad einen Lustigen Post von dir in der Delphi-Praxis gelesen:
http://www.delphipraxis.net/topic63109,0,asc,15.html, in dem du behauptest, dass 4294967294000017 eine Primzahl ist, was sie aber garnicht ist: 657413 * 6533134109 .. Da stimmt irgendetwas mit deinem Algo net ;) . Und bei mir hat der Test 200ms gedauert oO
Horst_H - Mo 21.11.05 13:34
Hallo,
warum weiss ich aber ;-).
Das ist wheel sieving.
Man kann eine Abfolge von Zahlendifferenzen bestimmen, sodass die entstehenden Zahlen immer Teilerfremd zu bestimmten Zahlen sind, am besten aufsteigende Primzahlen.
Die Anzahl der Elemente bis sich alles wiederholt (Perioden Laenge) ist Produkt(p(i)-1)
Zahlengerade:
Quelltext
1: 2: 3: 4: 5: 6:
| 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 1 +1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1 mod 2 1 +2| +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 +2 : Anzahl 1(2-1) mod 3 1 +4 +2| +4 +2 +4 +2 +4 +2 +4 +2 +4 : Anzahl 2(2-1)*(3-1) mod 5 1 +6 +2 +4 +2 +4 +2 +4 +6 +2| +6 : Anzahl 8(2-1)*(3-1)*(5-1) |
Das laesst sich beliebig steigern. So hat jemand, der die Differenzen aufeinanderfolgender Primzahlen untersuchte zum Abspeichern das Rad bis 23 genommen, das verdichtet die Primzahlen als Bitfeld um
Produkt (pi-1)/pi (i=1..9,pi = (2,3,5,7,11,13,17,19,23)) aber lohnt nicht so recht.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116:
| const BIS = 5; MAXIMUM =100000000; Prim :array [0..9] of byte = (2,3,5,7,11,13,17,19,23,29); MAXANZAHL :array [0..8] of Longint =(2,6,30,210,2310,30030, 510510,9699690,223092870); WIFEMAXLAENGE :array [0..8] of longint =(1,2,8,48,480,5760, 92160,1658880,36495360); PRIMANZAHL :array[0..8] of longint =(1,3,12,48,345,3250, 42333, 646029,12283531);
MAXMULLAENGE = 22; MAXSUCHE = (((MAXIMUM div 231)*48-1)shr 5+1)shl 5; type toffset = record Sum, Offs :longint; end; tMulFeld = array [2..MAXMULLAENGE] of tOffset; tZahlenFeld = array [0..30030] of LongWOrd ; tDiffFeld = array [0..5760-1] of byte;
tSuchFeld = array [0..MAXSUCHE shr 5+1] of set of 0..31; var ... Begin WiFeLaenge := 1; MaxZahl := 2; DiffFeld[0] := 2;
for loop := 1 to bis do begin
Diff := DiffFeld[0]; Suchprim := Diff+1;
inc(Anzahl); Write('>',MaxZahl:10,SuchPrim:10,'<');
MaxZahl := MaxZahl*SuchPrim; WiFeLng_1 := WiFeLaenge-1;
offset := WiFeLAenge; for i := 1 to Suchprim shr 1 do Begin move(DiffFeld[0],DiffFeld[Offset],WiFeLaenge*SizeOf(DiffFeld[0])); inc(offset,WiFeLaenge); end;
Summe := 1; j := WiFeLaenge shr 1; If j <> 0 then dec(j); For i := 0 to j do begin Zahlen[i] := Summe*SuchPrim; inc(Summe,DiffFeld[i]); end; Zahlen[j+1] := 0;
Summe := 1; MulPos := 0; I := 0; J := 0; Diff := Suchprim; k := MaxZahl shr 1; while Summe < k do Begin inc(Summe,DiffFeld[i]); DiffFeld[j]:= DiffFeld[i]; If Summe=Diff then Begin inc(MulPos); Diff := Zahlen[MulPos]; inc(i); inc(DiffFeld[j],DiffFeld[i]); inc(Summe,DiffFeld[i]) end; inc(i); inc(j); end; WiFelaenge := WiFeLaenge*(Suchprim-1); k := WiFelaenge shr 1-1; WiFeLng_1 := WiFeLaenge-2; For i := 0 to k do DiffFeld[WiFeLng_1-i] := DiffFeld[i]; DiffFeld[WiFeLaenge-1] := 2; end; |
Damit koennte man sich zum Beispiel das Differenzenfeld aufbauen und zyklisch abarbeiten.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
| TestZahl := EingabeZahl; n=1; Index := 0; Grenze := trunc(Sqrt(TestZahl)); repeat n := n+ diffFeld[Index]; IF TestZahl Mod n = 0 then begin Exp := 1; TestZahl := TestZahl div n; while TestZahl mod n = 0 do begin inc(Exp); TestZahl := TestZahl div n; end; write('*',n); If exp >1 then write('^',Exp); end; inc(index); If Index >=WIFEMAXLAENGE[BIS] then Index :=0; until n > Grenze; |
Alles Kokolores.
Zur schnellen Bestimmung mittels einfacher Division nehme man ein Primzahlsiebprogramm und dort wo gezaehlt wird einfach den Test auf teilbarkeit ohne Rest ausfuehren.
Es ist Unsinn vorher jeden Teiler auf prim zu testen, da das immer laenger als eine Division dauert.
Gruss Horst
Moderiert von
Christian S.: Delphi-Tag repariert
BenBE - Mo 21.11.05 14:47
Noch ein paar Optimierungen:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50:
| function PrimFaktorZerlegung (Zahl: Int64): String; var Root: Integer; Teiler : Int64; begin Result := '';
While Zahl and 1 = 0 do Begin Result := Result + '2 * '; Zahl := Zahl shr 1; end;
if zahl >= 2 then Begin while Zahl mod 3 = 0 do begin Result := Result + '3 * '; Zahl := Zahl div 3; end;
Root := Trunc(SQRT(1.0 * Zahl)); Teiler := 5;
while Teiler <= Root do begin while Zahl mod Teiler = 0 do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; Root := Trunc(SQRT(1.0 * Zahl)); end; Inc(Teiler, 2);
while Zahl mod Teiler = 0 do begin Result := Result + IntToStr(Teiler) + ' * '; Zahl := Zahl div Teiler; Root := Trunc(SQRT(1.0 * Zahl)); end; Inc(Teiler, 4); end; end;
if zahl <> 1 then Result := Result + IntToStr(Zahl) + ' * ';
Delete(Result, Length(Result) - 2, 3); end; |
Bringt nochmal ~7 Sekunden.
Born-to-Frag hat folgendes geschrieben: |
Ok damit brauch ich jetzt nur noch 140Sekunden.. |
Bei solchen Messungen sind immer CPU-Geschwindigkeit und CPU-Typ interessant. Bei mir AMD XP 1600+ bei 1,4GHz...
Womit misst Du deine Zeiten?
Born-to-Frag hat folgendes geschrieben: |
Noch ein paar fragen dazu:
Warum nicht gleich while Teiler <= Trunc(Sqrt(1.0 Zahl))? |
Hab ich daher ausgelagert, da sich dieser Wert (Bei mir Root) nur selten Ändert, aber lange zum Berechnen brauch.
Born-to-Frag hat folgendes geschrieben: |
Warum shr und While Zahl and 1 = 0? |
Bitoperationen können schneller als reguläre Divisionen ausgeführt werden.
Born-to-Frag hat folgendes geschrieben: |
Warum reicht es, die Zahl immer im 2 und dann um 4 zu erhöhen? |
Steht im Thread Optimieren einer Primzahlfunktion hier in der Sparte genauer drin.
Geht im wesentlichen darum, dass Du das Sieb des Erastosteles unter der Annahme, dass Dir mehr als eine Primzahl (z.B. 2und 3) gegeben sind, periodische Folgen ermitteln kannst, die keine Primzahlen sein können.
D.h. bei 2 und 3 hast Du Periodenlänge 6 und folgende Zahlen, die Du ausschließen kannst:
6x,
6x+2,
6x+4 (weil durch 2 teilbar, sowie
6x und
6x+3 (weil durch 3 teilbar).
Born-to-Frag hat folgendes geschrieben: |
greetz
EDIT: Ich hab da mal was gehighlighted wovon ich glaube das es doppelt ist.. |
Jup. War echt doppelt ;-) Hab's in der neuen Version optimiert ;-)
@Horst: Ich bezweifel, dass für meinen Algo ein Loop-Unwinding für WheelSieving (2,3,5) großartige Vorteile bringt ...
Born-to-Frag - Mo 21.11.05 14:55
Also ich messe mit einem P4 3.0GHz mit HT.. 137703ms.
Warum while Zahl and 1 = 0? shr versteh ich auch immer noch nicht ganz. Ich werd in der Zeit mal in der Delphi Hilfe nachsehen ;)
greetz
EDIT: ganz vergessen :oops: Zahl ist 9223372036854775806
Shr ist jetzt klar ;)
BenBE - Mo 21.11.05 15:02
Born-to-Frag hat folgendes geschrieben: |
Warum while Zahl and 1 = 0? shr versteh ich auch immer noch nicht ganz. Ich werd in der Zeit mal in der Delphi Hilfe nachsehen ;) |
Das wirst Du in der DOH nicht finden ;-)
Hängt mit der bionären Darstellung von Zahlen zusammen:
Mit X and 1 maskierst Du das letzte bit einer zahl und erhälst damit den Rest der Division durch 2. AND ist eine Bitoperation, die von der CPU in einem Zyklus ausgeführt werden kann. Eine Division benötigt IIRC 10++ Zyklen für's gleiche ...
Born-to-Frag - Mo 21.11.05 15:06
Mit dem optimiertem Algo brauch ich jetzt noch ~131Sekunden :)
Horst_H - Mo 21.11.05 15:32
Hallo,
es geht doch garnicht um Loop unwinding.
Bei mir dauert ein Durchlauf hierfuer:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21:
| var time : TdateTime; Test1,Test2,Test3: int64; begin t := time; Test1 := cardinal(65537)*Cardinal(65531); Test1 := Test1*65517*32019; Test3 := 0; repeat inc(Test3); Test2 := Test1 mod Test3; until Test3 >= 10000000; t := time-t; writeln(Test3); writeln(Test2); writeln(Test1); writeln(FOrmatDateTime('hh:mm:ss.zzz ',t)); writeln(Format('%e',[Test3/(t*86400)])); end; |
4.016 Sekunden fuer 1e7 Rechnungen bei 1.953e9 Hz
umgerechnet 784 Cpu Takte.
Also rechnet sich das mit DiffFeld.
Dein DiffFeld ist das Bis=1
DiffFeld Mod 2,3 rechnet 2/6=0.3333 aller Zahlen
Bis = 1..5 -> relAnzahl 0.333333;0.266666;0.22857;0.20779;0.19181
Das bedeutet ein Diffeld mit 5760 Feldern ergibt eine Berechnug fuer 0.1981 aller Zahlen bis root.
root ~4e9 /3 = 1.33 e9 Berechnungen
bei Bis =5 sind es 0.792e9 mod Berechnungen
also rund 60% wobei die Additionen ja sehr schnell sind.
Bei mir bestimmt das Primzahlesieb die Zahlen aber wesentlich schneller. 295 Takte mit dem alten TP Programm und mit alzaimar's unter 100 Takte pro Zahl.
Bis zu 1e9 sind es ja nur 50.8 Mio Primzahlen, also liegt dort ein Gewinn, statt mit 333 Mio Zahlen zu teilen.
Gruss Horst
Horst_H - Mo 21.11.05 16:00
Hallo,
ist das Ergebnis:
9223372036854775805 = 5*23 * 53301701 * 1504703107?
Dass dauert 9.5 Sekunden.(Mit DiffFeld Mod 2,3,5,7,11,13 nur 3.7 Sekunden)
Gute Guete wer rechnet denn da staendig eine Wurzel ausserhalb der while Schleife, dass ist mir beim lesen nicht aufgefallen.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48:
| function PrimFaktorZerlegung (Zahl: Int64): String; var Teiler, Root: Cardinal; Index : integer; begin Result := ''; While Zahl and 1 = 0 do Begin Result := Result + IntToStr(2) + ' * '; Zahl := Zahl shr 1; end; if zahl >= 2 then Begin Root := Trunc(SQRT(Zahl)); Teiler := 1; Index := 0; repeat inc(Teiler,2); IF Zahl mod Teiler = 0 then begin repeat Zahl := Zahl div Teiler; Result := Result + IntToStr(Teiler) + ' * '; until Zahl mod Teiler <> 0; Root := Trunc(SQRT(Zahl)); end; until Teiler > Root; end; if zahl <> 1 then begin Result := Result + IntToStr(Zahl) + ' * ';; end; Delete(Result, Length(Result) - 2, 3); end; |
Gruss Horst
Born-to-Frag - Mo 21.11.05 16:10
Hallo,
erm.. wir reden ja auch nicht von 9223372036854775805, sondern von 9223372036854775806 ;)
Also bei deiner Variante dauert 9223372036854775806 = 205938ms! Bei dem davor nur ~131 Sekunden!
9223372036854775805 dauert bei dem verbesserten von BenBe nur ~10Sekunden
Keiner rechnet hier Wurzeln außerhalb von while schleifen. Das ist doch schon längst verbessert das die Wurzel nur ausgerechnet wird wenn sich wirklich was ändert.
Also ist deiner langsamer
greetz
Edit: 9223372036854775805 hat bei mir bei deiner Variante auch 13Sekunden gedauert!
Es ist ja auch klar warum dein Algo 200sek braucht: Weil du immer nur im 2 erhöhst...
Horst_H - Mo 21.11.05 17:17
Hallo,
es dauert mit +2,+4 bei mir
Quelltext
1: 2:
| 9223372036854775806 = 2 * 3 * 715827883 * 2147483647 00:01:22.796 |
wenn ich das DiffFeld mod 2,3,5,7,11,13 einsetze sind es:
Quelltext
1: 2:
| 9223372036854775806 = 2 * 3 * 715827883 * 2147483647 00:00:46.031 Sekunden |
das ist doch ein wenig besser, aber eben nicht optimal.
Gruss Horst
Born-to-Frag - Mo 21.11.05 17:40
Horst_H hat folgendes geschrieben: |
Hallo,
ist das Ergebnis:
9223372036854775805 = 5*23 * 53301701 * 1504703107?
Dass dauert 9.5 Sekunden.(Mit DiffFeld Mod 2,3,5,7,11,13 nur 3.7 Sekunden)
Gute Guete wer rechnet denn da staendig eine Wurzel ausserhalb der while Schleife, dass ist mir beim lesen nicht aufgefallen.
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48:
| function PrimFaktorZerlegung (Zahl: Int64): String; var Teiler, Root: Cardinal; Index : integer; begin Result := ''; While Zahl and 1 = 0 do Begin Result := Result + IntToStr(2) + ' * '; Zahl := Zahl shr 1; end; if zahl >= 2 then Begin Root := Trunc(SQRT(Zahl)); Teiler := 1; Index := 0; repeat inc(Teiler,2); IF Zahl mod Teiler = 0 then begin repeat Zahl := Zahl div Teiler; Result := Result + IntToStr(Teiler) + ' * '; until Zahl mod Teiler <> 0; Root := Trunc(SQRT(Zahl)); end; until Teiler > Root; end; if zahl <> 1 then begin Result := Result + IntToStr(Zahl) + ' * ';; end; Delete(Result, Length(Result) - 2, 3); end; |
Gruss Horst |
Reden wir hier vom gleichen? Also ich komme mit dieser funktion auf ~190sek
Und mit der optimieren auf ~131sekunden..
BenBE - Mo 21.11.05 18:03
k, hab jetzt n Algo mit folgender Ausgabe:
Quelltext
1: 2: 3: 4: 5: 6: 7:
| Zu faktorisierende Zahl: 9223372036854775806 2 3 715827883 2147483647 Faktorisierung abgeschlossen! Benötigte Zeit: 85,599047 s |
Source dafür sieht so aus:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183:
| Program WheelCalc;
{$APPTYPE CONSOLE}
Uses Windows, SysUtils, Classes,
ODbgInterface, ODbgMapfile, OIncTypes;
Var Factorize: Int64;
Const PrimeCount = 8; Var Wheel: Array Of Integer; WheelLen: Integer; WheelPeriod: Int64;
X, X2: Integer; Y, Z: Int64; CurrPrime: Integer;
WheelPtrSrc, WheelPtrDest: Integer;
Procedure HandleError(Level: Integer; Place: TODbgMapLocation; Desc: String; Var ErrorObj: Exception; Var Handled: Boolean); Var SL: TStringList; Begin WriteLn('Exception Level ', Level, ' occured at the following location:'); WriteLn(' ', PlaceToLocationStr(Place)); WriteLn('The Stacktrace is as follows:'); SL := GetStackTrace(10); While SL.Count > 0 Do Begin WriteLn('- ', SL[0]); SL.Delete(0); End; End;
Var T1, T2, T3: Int64;
Begin AddErrorHandler(HandleError); Write('Zu faktorisierende Zahl: '); ReadLn(Factorize);
If Factorize < 0 Then Begin WriteLn('-1'); Factorize := -Factorize; End; If Factorize < 2 Then Begin If Factorize = 0 Then Write('0'); Exit; End;
QueryPerformanceCounter(T1); X2 := 0; While Factorize And 1 = 0 Do Begin Factorize := Factorize Shr 1; Inc(X2); End; If X2 <> 0 Then Begin If X2 = 1 Then WriteLn('2') Else WriteLn('2 ^ ', X2); End; If Factorize = 1 Then Break;
WheelLen := 1; SetLength(Wheel, WheelLen); Wheel[0] := 2; WheelPeriod := 2;
For X := 0 To PrimeCount - 1 Do Begin CurrPrime := 1 + Wheel[0];
X2 := 0; While Factorize Mod CurrPrime = 0 Do Begin Factorize := Factorize Div CurrPrime; Inc(X2); End; If X2 <> 0 Then Begin If X2 = 1 Then WriteLn(CurrPrime) Else WriteLn(CurrPrime, ' ^ ', X2); End; If Factorize = 1 Then Break;
WheelPeriod := WheelPeriod * CurrPrime;
SetLength(Wheel, WheelLen * CurrPrime); For X2 := 1 To CurrPrime - 1 Do Move(Wheel[0], Wheel[WheelLen * X2], WheelLen * SizeOf(Wheel[0]));
WheelPtrSrc := 0; WheelPtrDest := 0; Y := 1;
While WheelPtrSrc < WheelLen * CurrPrime Do Begin Z := 0; Repeat Y := Y + Wheel[WheelPtrSrc]; Z := Z + Wheel[WheelPtrSrc]; Inc(WheelPtrSrc); Until (Y Mod CurrPrime <> 0) Or (WheelPtrSrc >= WheelLen * CurrPrime); Wheel[WheelPtrDest] := Z; Inc(WheelPtrDest); End;
WheelLen := WheelPtrDest; SetLength(Wheel, WheelLen); End;
WheelPtrSrc := 1; Y := 1 + Wheel[0] + Wheel[1]; Z := Trunc(Sqrt(1.0 * Factorize)); While Y < Z Do Begin X2 := 0; While Factorize Mod Y = 0 Do Begin Factorize := Factorize Div Y; Inc(X2); End; If X2 <> 0 Then Begin If X2 = 1 Then WriteLn(Y) Else WriteLn(Y, ' ^ ', X2);
Z := Trunc(Sqrt(1.0 * Factorize)); End; If Factorize = 1 Then Break;
Inc(WheelPtrSrc); If WheelPtrSrc >= WheelLen Then WheelPtrSrc := 0; Y := Y + Wheel[WheelPtrSrc]; End;
If Factorize <> 1 Then WriteLn(Factorize);
QueryPerformanceCounter(T2); QueryPerformanceFrequency(T3);
WriteLn('Faktorisierung abgeschlossen!'); WriteLn(Format('Benötigte Zeit: %.6f s', [(T2 - T1) / T3])); ReadLn; End. |
Die mit
gekennzeichneten Teile hab ich aus Debugging-Gründen aufgenommen, damit ich im Fehlerfalle die genaue Position des Fehlers leichter finden kann ;-) Z.B. darf PrimeCount MAXIMAL 9 sein, weil das Programm ansonsten mit einem RangeError abstürzt (weil das Wheel-Array mehr als 4GB benötigen würde).
//Nachtrag:
Quelltext
1: 2: 3: 4: 5: 6: 7:
| Zu faktorisierende Zahl: 9223372036854775805 5 23 53301701 1504703107 Faktorisierung abgeschlossen! Benötigte Zeit: 10,967501 s |
Born-to-Frag - Mo 21.11.05 18:55
Kann ich nicht testen :( Hab ja die ganzen uses nicht..
greetz
Horst_H - Mo 21.11.05 19:04
Hallo,
das ist naturidentisch mit der Version die ich Dir gesendet habe, nur wesentlich uebersichtlicher.Damals wollte ich auch Platz sparen (29 Mb fuer die Zahlen bis 1e9).
Gruss Horst
BenBE - Mo 21.11.05 19:09
Ich schrieb doch: Die durch Kommentare markierten Teile einfach auskommentieren. Das ist nur mein Error-Handler sowie eine Unit, die sich drt mit reinhängt.
Born-to-Frag - Mo 21.11.05 19:25
Ok, mein fehler Ben ;) Ich sehs mir noch mal an.. auch deine Version Horst ;)
BenBE - Mo 21.11.05 20:46
alzaimar hat folgendes geschrieben: |
Ein schnelles Programm in C mit Sourcen (starker Tobak). |
Weil das Programm schnell ist, oder weil's mit Sources ist (SCNR)??? :mrgreen:
Born-to-Frag - Di 22.11.05 00:19
Sind beide extrem kompliziert.. Noch ne Frage: Meine IsPrimzahl function ist doch Sinnlos geworden?! Denn wenn die Zahl keine Primzahl ist, wurde die function doppelt durchlafen. Und selbst wenn sie eine ist, würde das meine PrimFaktorZelegung - function schneller herausfinden!
Oder täusche ich mich da?
greetz
PS: Ich probiers morgen nochmal genauer aus, heut min ich zu müde ;)
Horst_H - Di 22.11.05 11:48
Hallo,
IsPrimzahl ist nun obsolet geworden, da es doppelte Rechnerei ist
Wie ich schon weiter oben gezeigt habe, ist die Berechnung des Restes extrem langsam mit ueber 700 CPU Takten.
Wenn man das Programm in der CPU Ansicht verfolgt erkennt man das @_llDIV tatsaechlich Bitweise arbeitet und deshalb alles sooooo laaaangsaaaaamm.
Damit es etwas schneller wird, nicht umsonst benutzt das qsieve Programm auch die GMP - Bibliotheken, etwas problemspezifischeres.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56:
| function Int64divModCar(Teiler : Cardinal;var Dividend,Quotient:Int64):Cardinal;assembler; asm Push EBX MOV EBX,EAX MOV EAX,[EDX+4] Push EDI MOV EDI,EDX XOR EDX,EDX TEST EAX,EAX MOV [ECX+4],EDX JE @einstellig DIV EBX MOV [ECX+4],EAX @einstellig: MOV EAX,[EDI] POP EDI DIV EBX POP EBX MOV [ECX],EAX MOV EAX,EDX; end;
if Int64divModCar(Teiler,TestZahl,Quotient) = 0 then begin repeat Result := Result + IntToStr(Teiler) + ' * '; Zahl := Quotient; until INt64divModCar(Teiler,TestZahl,Quotient) <> 0; Root := Trunc(SQRT(Zahl)); end;
procedure TForm1.Button2Click(Sender: TObject); var i,k : cardinal; Dividend, Quotient : int64; T : tDateTime; begin Dividend := 1001*65535; Dividend := Dividend *65537; i := 1; t := time; repeat k := INt64divModCar(i,Dividend,Quotient); inc(i); until i > 100000000; t := time-t; memo1.Lines.Add(FormatdateTime('hh:mm:ss.zzz',t)); end; |
Damit braucht es bei mir Primfaktorzerlegung von ????...06 statt 46 Sekunden noch 6,6.
Die 1e8 Durchlaeufe von Button2Click dauerten 4.61 Sekunden =>
4.61/1e8* 1.953e9 = 90 Takte fuer einen Schleifendurchlauf statt 734 bringt dass schon einen Faktor 8
Gruss Horst
P.S.:
9223372036854775806 = 2 * 3 * 715827883 * 2147483647 Anzahl Divisionen: 137.301.661 max Teiler: 715827883
Das bedeutet bei 6.6 Sekunden == 94 Takte pro Durchlauf, sagenhaft nah an der reinen minimalste Schleife.
Bis 715827883 sind es 37.029.521 Primzahlen
Damit lohnt sich mein vorgeschlagenes Verfahren, innerhalb einer Primzahlsiebschleife den Test durchzufuehren, auch erst, wenn die Bestimmung pro Primzahl unter 2.7(=137Mio/37mio-1) *90 Takte= 240 sinkt.
Mit alzaimar's Sieb ala Atkin's sind es 57 Takte ! (1.5 Sekunden fuer die ersten 50.8 Mio Primzahlen).
Theoretische minimalste Zeit ~ 37e6*(57(finden)+90(testen))(Takte)= 5.439e9 Takte oder 2.78 Sekunden (div f-CPU)
Das ist nochmals eine Beschleunigung um das 2.37 fache.
Damit ist fuer mich das Ende der Fahnenstange fuer einfache Probedivision erreicht.
Jetzt hilft nur noch der Uebergang auf andere Algorithmen
http://de.wikipedia.org/wiki/Pollard-Rho-Methode ,da steht zwar etwas von wenigen Durchlaeufen, aber GCD bekommt man auch nicht geschenkt.
Born-to-Frag - Di 22.11.05 16:11
Ja gut, mit Assemblern kenn ich mich noch nich so gut aus :oops:
Muss mich mal reinarbeiten
greetz
Horst_H - Do 24.11.05 14:39
Hallo,
Hier [
http://www.polarhome.com:793/~franco/] gibt es eine Unit BigInt, die fast komplett in Assembler geschrieben ist.Anbei ist dort auch eine Faktorisierung nach Pollard-Rho.
Das Prinzip ist einfach. statt GGT(TestZahl, kleineZahl)<>1 wird GGT(TestZahl,RiesigeTestzahl aus moeglichst vielen unterschiedlichen Faktoren) gebildet.
Wobei die Funktion, die die Testzahlen erzeugt, moeglichst GGT(f(x_i),f(x_j))=1 fuer i<>j liefern sollte.
Das heisst es werden immer neue Faktoren getestet.
Auch fuer die Bestimmung von Pi , kann man diese Bibliothek nutzen.
Gruss Horst
F34r0fTh3D4rk - Fr 21.04.06 16:04
Es gibt doch noch dieses Gittersieb-Verfahren, welches als eines der schnellsten gilt, ich hab nichts genaueres dazu gefunden, vielleicht hat jemand mal den algo (von mir aus auch als formulierung) ? [Zahlkörpersieb]
die version die ich momentan in meiner unit verwende ist diese hier:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50:
| function p_primfaktor(a: int64): TPrimfaktor; var Teiler: Int64; begin if a < 0 then begin SetLength(result, 1); result[0] := -1; a := -a; end; while (a and 1) = 0 do begin setlength(result, length(result) + 1); Result[high(Result)] := 2; a := a shr 1; end; if a >= 2 then begin Teiler := 3; while a mod Teiler = 0 do begin setlength(result, length(result) + 1); Result[high(Result)] := Teiler; a := a div Teiler; end; Inc(Teiler, 2); while Teiler <= Trunc(SQRT(1.0 * a)) do begin while a mod Teiler = 0 do begin setlength(result, length(result) + 1); Result[high(Result)] := Teiler; a := a div Teiler; end; Inc(Teiler, 2); while a mod Teiler = 0 do begin setlength(result, length(result) + 1); Result[high(Result)] := Teiler; a := a div Teiler; end; Inc(Teiler, 4); end; end; if a <> 1 then begin setlength(result, length(result) + 1); Result[high(Result)] := a; end; end; |
wobei ich mir nicht sicher bin, ob
Inc(Teiler, 4); richtig ist, es ist imgrunde nur die array variante ohne die variable root
Horst_H - Fr 21.04.06 16:37
Hallo,
Siehe Seite 2 diese Threads ab :
Verfasst am: Mo 21.11.05 12:15
Es geht nur um wheel-sieving, bei Dir nur ohne die Vielfachen von 2,3.
Ich glaube nicht das Du damit unter 6,6 Sekunden kommst.(1.8 Ghz Ahtlon64 , Duron 1800 ist 10% schneller, da schneller beim dividieren)
Gruss Horst
F34r0fTh3D4rk - Fr 21.04.06 17:27
ist wheel sieving das gleiche ?
Horst_H - Fr 21.04.06 18:02
Hallo,
im Prinzip ja.
Es ist einfach das Ausstreichen mit zu einer Reihe von Primzahlen (2,3,5,7,11,13...)teilerfremden Zahlen.
Also erst mit den kleinen Primzahlen probedividieren und anschliessend nur mit Zahlen die zu diesen teilerfremd sind.
Dabei ergibt sich ein regelmaessiges Muster von Differenzen.
Dein Beispiel benutzt eben nur zu 2,3 teilerfremde Zahlen.mit dem Muster +2,+4 (2+4=6=2*3)
Gruss Horst
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!