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 > 2and (Teiler mod 2 = 0then Inc(Teiler);
    if IsPrimzahl(Zahl) = True then Teiler := Zahl;
    while (Zahl > 1and (Zahl mod Teiler = 0do 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

user profile iconBorn-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 > 1and (Zahl mod Teiler = 0do 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

user profile iconAllesquarks 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! )


user profile iconAllesquarks 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

user profile iconAllesquarks 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 > 1do begin  

 
     while (Teiler<Zahl) AND (Zahl mod Teiler = 0)do begin  
      Result := Result + IntToStr(Teiler) + ' * ';  
      Zahl := Zahl div Teiler;  
      // unnötig if IsPrimzahl(Zahl) = True then Teiler := Zahl;  
    end;  
    Inc(Teiler, 2);  
  end
 if not(zahl=1then begin
//Primfaktoren.add(zahl);
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;  
      // unnötig if IsPrimzahl(Zahl) = True then Teiler := Zahl;  
    end;  
    Inc(Teiler, 2);  
  end
end;
   SetLength(Result, Length(Result) - 3);  
end;


Born-to-Frag - So 20.11.05 21:17

user profile iconAllesquarks 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 > 1do begin  

 
     while (Teiler<Zahl) AND (Zahl mod Teiler = 0)do begin  
      Result := Result + IntToStr(Teiler) + ' * ';  
      Zahl := Zahl div Teiler;  
      // unnötig if IsPrimzahl(Zahl) = True then Teiler := Zahl;  
    end;  
    Inc(Teiler, 2);  
  end
 if not(zahl=1then begin
//Primfaktoren.add(zahl);
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;  
      // unnötig if IsPrimzahl(Zahl) = True then Teiler := Zahl;  
    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;    
      // unnötig if IsPrimzahl(Zahl) = True then Teiler := Zahl;    
    end;    
    Inc(Teiler, 2);    
  end;   
end;  
//dann jetzt aber doch 
if not(zahl=1then begin
//zahl ist Primzahl also noch in den String dazuschreiben
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

user profile iconAllesquarks 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;

    //dann jetzt aber doch 
    if zahl <> 1 then
    begin
        Raise Exception.Create('Kann nicht sein!!!');
    end;
    Delete(Result, Length(Result) - 23);    
end;


Ungetestet, ob er wirklich schneller ist ... Müsste aber ...


Born-to-Frag - Mo 21.11.05 00:07

user profile iconBenBE 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;

    //dann jetzt aber doch 
    if zahl <> 1 then
    begin
        Raise Exception.Create('Kann nicht sein!!!');
    end;
    Delete(Result, Length(Result) - 23);    
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:
//dann jetzt aber doch   
    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;

    //dann jetzt aber doch 
    if zahl <> 1 then
        Result := Result + IntToStr(Zahl) + ' * ';

    Delete(Result, Length(Result) - 23);    
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

user profile iconBenBE 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;

    //dann jetzt aber doch 
    if zahl <> 1 then
        Result := Result + IntToStr(Zahl) + ' * ';

    Delete(Result, Length(Result) - 23);    
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

user profile iconBorn-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

user profile iconalzaimar 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..9of byte = (2,3,5,7,11,13,17,19,23,29);
  {MAXANZAHL     =  2*3*5*7*11*13*17*19;*PRIM}
  MAXANZAHL     :array [0..8of Longint =(2,6,30,210,2310,30030,
                                         510510,9699690,223092870);
  {WIFEMAXLAENGE =  1*2*4*6*10*12*16*18;*PRIM-1}
  WIFEMAXLAENGE :array [0..8of longint =(1,2,8,48,480,5760,
                                         92160,1658880,36495360);
 {Anzahl der wirklichen PrimZahlen im Wiederholungsfeld}
  PRIMANZAHL    :array[0..8of longint  =(1,3,12,48,345,3250,
                                         42333646029,12283531);
{ Die maximale Differenz die im Wiederholungsfeld  vorkommt}
  MAXMULLAENGE  = 22{array [0..9] of byte= (2,4,6,10,14,22,26,34,40,50); 40,50 geraten}
                   {Auf  Mod 32 = 0 bringen}
  MAXSUCHE      = (((MAXIMUM div 231)*48-1)shr 5+1)shl 5{MAXIMUM*WFEMAXLAENGE[BIS]/MAXANZAHl[BIS]}
                  {div2, div3,*4div15,*24div105,*48div231,*3072div17017...}
type
  toffset       = record
                     Sum,
                     Offs :longint;
                  end;
  tMulFeld    = array [2..MAXMULLAENGE] of tOffset;
  tZahlenFeld = array [0..{MAXANZAHL[BIS]-1}30030of LongWOrd  ;
  tDiffFeld   = array [0..{WIFEMAXLAENGE[BIS]}5760-1of byte;

  (* Zahlen feld als Bit array *)
  tSuchFeld   = array [0..MAXSUCHE shr 5+1of set of 0..31;
var 
  ...
Begin
   WiFeLaenge := 1;
   MaxZahl := 2;
   DiffFeld[0] := 2;
   {Das Rad aufbauen}

for loop := 1 to bis do
   begin

   Diff := DiffFeld[0];
   Suchprim := Diff+1;

{   PrimFeld[Anzahl] := Suchprim;}
   inc(Anzahl);
   Write('>',MaxZahl:10,SuchPrim:10,'<');

   MaxZahl := MaxZahl*SuchPrim;
   WiFeLng_1 := WiFeLaenge-1;

  {Hintereinander Schreiben des neuen WiederholungsFeldes}
  {Dabei wird das WiederholungsFeld insgesamt SuchPrim div 2}
  {hintereinander geschrieben }
   offset := WiFeLAenge;
   for  i := 1 to Suchprim shr 1 do
   Begin
      move(DiffFeld[0],DiffFeld[Offset],WiFeLaenge*SizeOf(DiffFeld[0]));
      inc(offset,WiFeLaenge);
   end;{For i}

   {Vielfache heraustrennen}

   {Berechnen der Vielfachen}

   Summe := 1;
   j := WiFeLaenge shr 1;
   {Aber mindestens einer, der ist aber immer 1*Suchprim}
   If j <> 0 then dec(j);
   {Die Berechnung der Vielfachen}
   {Diese werden in dem DiffFeld von ober nach unten abgespeichert,}
   {da dieser Platz momentan nicht genutzt wird}
   For i := 0 to j do
   begin
      Zahlen[i] := Summe*SuchPrim;
      inc(Summe,DiffFeld[i]);
   end;
   Zahlen[j+1] := 0;

   {Die Vielfachen jetzt finden}
   Summe := 1;
   MulPos := 0;
   I := 0;
   J := 0;
  {Diff wird als Platzhalter der Vielfachen von Suchprim benutzt}
   Diff := Suchprim;
   {Bis zu welcher Summe gesucht werden muss}
   k := MaxZahl shr 1;
   while Summe < k do
   Begin
      inc(Summe,DiffFeld[i]);
      DiffFeld[j]:= DiffFeld[i];
      If Summe=Diff then
      Begin
         {Den naechsten Vielfachen hervorkramen}
         inc(MulPos);
         Diff := Zahlen[MulPos];
         {Den Vielfachen jetzt heraustrennen}
         inc(i);
         {Die neue Differenz eintrageen}
         inc(DiffFeld[j],DiffFeld[i]);
         {Die Summe korrigeren}
         inc(Summe,DiffFeld[i])
      end{If}
      inc(i);
      inc(j);
   end{WHILE}
   {Die neue Wiederholungsfeldlaenge}
   WiFelaenge := WiFeLaenge*(Suchprim-1);
   {Spiegeln}
   k :=  WiFelaenge shr 1-1;
   WiFeLng_1 := WiFeLaenge-2;
   For i := 0 to k do
      DiffFeld[WiFeLng_1-i] := DiffFeld[i];
   {Das letzte Feld ist immer 2}
   DiffFeld[WiFeLaenge-1] := 2;
end{For Loop}


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 user profile iconChristian 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;

    //dann jetzt aber doch 
    if zahl <> 1 then
        Result := Result + IntToStr(Zahl) + ' * ';

    Delete(Result, Length(Result) - 23);    
end;

Bringt nochmal ~7 Sekunden.

user profile iconBorn-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?

user profile iconBorn-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.

user profile iconBorn-to-Frag hat folgendes geschrieben:
Warum shr und While Zahl and 1 = 0?

Bitoperationen können schneller als reguläre Divisionen ausgeführt werden.

user profile iconBorn-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).

user profile iconBorn-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

user profile iconBorn-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;
  {
   For Index := 0 to BIS do
     While Zahl Mod and Prim[Index] = 0 do
       Begin
       Result := Result + IntToStr(Prim[Index]) + ' * '; 
       Zahl := Zahl DIV Prim[index];
       end;
   }

  if zahl >= 2 then
    Begin
    Root := Trunc(SQRT(Zahl));
    Teiler := 1;
    Index := 0;
    repeat
      inc(Teiler,2);
      //inc(Teiler,DiffFeld[index]);
      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));//Nur dann wenn sich wirklich was aendert
        end;
//      inc(index);
//      If index > High(DiffFeld) then  
//         index := 0;
    until Teiler > Root;
    end;
    //dann jetzt aber doch
    if zahl <> 1 then
      begin
      Result := Result + IntToStr(Zahl) + ' * ';;
      end;
    Delete(Result, Length(Result) - 23);
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

user profile iconHorst_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;
  {
   For Index := 0 to BIS do
     While Zahl Mod and Prim[Index] = 0 do
       Begin
       Result := Result + IntToStr(Prim[Index]) + ' * '; 
       Zahl := Zahl DIV Prim[index];
       end;
   }

  if zahl >= 2 then
    Begin
    Root := Trunc(SQRT(Zahl));
    Teiler := 1;
    Index := 0;
    repeat
      inc(Teiler,2);
      //inc(Teiler,DiffFeld[index]);
      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));//Nur dann wenn sich wirklich was aendert
        end;
//      inc(index);
//      If index > High(DiffFeld) then  
//         index := 0;
    until Teiler > Root;
    end;
    //dann jetzt aber doch
    if zahl <> 1 then
      begin
      Result := Result + IntToStr(Zahl) + ' * ';;
      end;
    Delete(Result, Length(Result) - 23);
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,

    //Omorphia Specific
    ODbgInterface, ODbgMapfile, OIncTypes;

Var
    Factorize: Int64;

Const
    PrimeCount = 8;
    //    Primes: Array[0..PrimeCount - 1] of Integer = (2,3,5,7,11,13,17,19,23,29,31);

Var
    Wheel: Array Of Integer;
    WheelLen: Integer;
    WheelPeriod: Int64;

    X, X2: Integer;
    Y, Z: Int64;
    CurrPrime: Integer;

    WheelPtrSrc, WheelPtrDest: Integer;

//Omorphia Specific:
Procedure HandleError(Level: Integer; Place: TODbgMapLocation; Desc: StringVar 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); //Omorphia Specific

    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;

    //Initialize the wheel
    WheelLen := 1;
    SetLength(Wheel, WheelLen);
    Wheel[0] := 2;
    WheelPeriod := 2;

    //Build up the wheel
    For X := 0 To PrimeCount - 1 Do
    Begin
        //Get the current prime for extension
        //CurrPrime := Primes[X];
        //Alternatively you could also write
        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;

        //Extend the whell to include as many field as required at first.
        SetLength(Wheel, WheelLen * CurrPrime);
        For X2 := 1 To CurrPrime - 1 Do
            Move(Wheel[0], Wheel[WheelLen * X2], WheelLen * SizeOf(Wheel[0]));

        //Start from the very beginning ;-)
        WheelPtrSrc := 0;
        WheelPtrDest := 0;
        Y := 1;

        //Until we finished checking all numbers of the current wheel
        While WheelPtrSrc < WheelLen * CurrPrime Do
        Begin
            Z := 0;
            Repeat
                Y := Y + Wheel[WheelPtrSrc];
                Z := Z + Wheel[WheelPtrSrc];
                Inc(WheelPtrSrc);
            Until (Y Mod CurrPrime <> 0Or (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 //Omorphia Specific 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 ;)


alzaimar - Mo 21.11.05 20:37

Wohin die Reise gehen kann, zeigt dieser Link:
http://www.thorstenreinecke.de/qsieve/

Ein schnelles Programm in C mit Sourcen (starker Tobak).


BenBE - Mo 21.11.05 20:46

user profile iconalzaimar 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;
//berechnet Int64 divmod Cardinal
//Gibt Rest zurueck
//EAX= Teiler ; EDX =Adresse von Zahl1; ECX=Adresse von Zahl2
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;

//und angewandt in der Primfaktorzerlegung:
  
  if Int64divModCar(Teiler,TestZahl,Quotient) = 0 then
//    If Zahl mod Teiler = 0 then
      begin
      repeat
        Result := Result + IntToStr(Teiler) + ' * ';
        Zahl := Quotient;
      until INt64divModCar(Teiler,TestZahl,Quotient) <> 0;
      Root := Trunc(SQRT(Zahl));
      end;

//zum testen:
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