Entwickler-Ecke

Algorithmen, Optimierung und Assembler - 196-Algorithmus


Mathematiker - Mi 04.07.12 20:21
Titel: 196-Algorithmus
Hallo,
eine interessante mathematische Fragestellung ist der 196-Algorithmus:
Gegeben ist eine zwei- oder mehrstellige natürliche Zahl n.
Zu dieser Zahl wird die natürliche Zahl addiert, welche dadurch entsteht, dass die Ziffernfolge von n umgekehrt wird. Diese Addition wird mit der Summe immer wiederholt, bis eine Palindrom-Zahl entsteht, z.B. n = 5280 mit den Gliedern 6105, 11121, 23232.
Die entstehende Zahl kann teilweise sehr groß werden, z.B. für 89 ergibt sich 8813200023188.
Die kleinsten Zahlen, für welche nicht bekannt ist, ob der Algorithmus irgendwann eine Palindrom-Zahl liefert, sind: 196, 887, 1675, 7436, 13783, …
Bis heute weiß niemand, ob im Dezimalsystem immer ein Palindrom entsteht. Im Dualsystem ist es sicher, dass nicht jede Zahl schließlich zu einem Palindrom führt. 10110 wird dort niemals palindromisch.

Mein kleines Programm sucht nach der Eingabe der Startzahl das Palindrom.
Das Problem ist nun, dass z.B. für n = 196 schnell die Schwierigkeit auftritt, die Zahl umzukehren und außerdem effizient im Speicher zu halten, da die Ziffernzahl scheinbar über alle Grenzen wächst.
Mir ist klar, dass mit einem einfachen Delphi-Programm das Palindrom der 196 nicht gefunden werden kann, da 2005 Wade van Landingham die Suche nach 284 Millionen Iterationen abbrechen musste, da das Folgenglied mehrere Millionen Ziffern hatte.
Meine Frage ist nun, ob jemand eine gute Idee hat, wenigstens bis zu 100000 Ziffern Länge in vertretbarer Zeit zu kommen.
Mein Algorithmus ist dafür ungeeignet. Ich verwandle die Zahl in einen String, drehe diesen Zeichen für Zeichen und transformiere diesen zurück, d.h. doppelte Speicherbelastung (Zahl und String) und langwierige String-Operationen.
Beste Grüße
Mathematiker


Martok - Mi 04.07.12 20:34

Hey, wie viele Probleme willst du hier eigentlich noch vorstellen? Wir brauchen ja bald eine Mathe-Sparte (nicht dass das schlecht wäre) :)

Ich hab da eine Idee - eigentlich braucht man ja nur Addition und Reverse, das braucht kein FGInt.


Mathematiker - Mi 04.07.12 21:17

Hallo Martok,
user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
Ich hab da eine Idee - eigentlich braucht man ja nur Addition und Reverse, das braucht kein FGInt.

Das bringt mich auf die Idee, String und FGInt fallen zu lassen und es einmal mit einem array of byte für die Ziffern zu versuchen. Die Addition dürfte dann relativ schnell gehen, natürlich mit dem Übertrag.
Ich werde es ausprobieren.
user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
Hey, wie viele Probleme willst du hier eigentlich noch vorstellen? Wir brauchen ja bald eine Mathe-Sparte (nicht dass das schlecht wäre)

Ich weiß, dass das hier ein Delphi(!)-Forum und kein Mathe-Forum ist. Leider gibt es ein Mathe-Forum, dass sich mit der programmtechnischen Umsetzung von Matheproblemen (die ich verstehe) beschäftigt, leider nicht. Ich werde mit etwas zurückhalten. :bawling:

Beste Grüße
Mathematiker


Martok - Mi 04.07.12 21:24

user profile iconMathematiker hat folgendes geschrieben Zum zitierten Posting springen:
Ich weiß, dass das hier ein Delphi(!)-Forum und kein Mathe-Forum ist. Leider gibt es ein Mathe-Forum, dass sich mit der programmtechnischen Umsetzung von Matheproblemen (die ich verstehe) beschäftigt, leider nicht. Ich werde mit etwas zurückhalten. :bawling:
Bloß nicht, ich finde das interessant :)
Solange uns das nicht die nächsten 100 Jahre Weihnachtsrätsel "verbrennt"... :zwinker:


Was hältst du davon? Ich hab gleich mal die GUI eingespart, die ersten 100k Iterationen (irgendwas um 43k Stellen, das scrollt so schnell vorbei :D ) dauern etwas weniger als 60 Sekunden.


Mathematiker - Mi 04.07.12 21:53

user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
Was hältst du davon? Ich hab gleich mal die GUI eingespart, die ersten 100k Iterationen (irgendwas um 43k Stellen, das scrollt so schnell vorbei :D ) dauern etwas weniger als 60 Sekunden.

Die Geschwindigkeit ist geradezu unglaublich. So habe ich mir das vorgestellt. Allerdings werde ich etwas brauchen, um Deinen Text zu verstehen.
Ich schäme mich fast, meine zweite Variante zu veröffentlichen, bei der ich auch Arrays nutze. Es ist viel langsamer als Dein Programm.
Beste Grüße
Mathematiker

Nachtrag: Ich habe mir ersteinmal FastMM4 besorgt, kannte ich natürlich nicht.
Beim Übersetzen Deines Textes fand mein Delphi
DivMod(Sum, NUMBER_BASE, Carry, Dig);
nicht. Kann das sein, dass in den aktuellen Delphi-Versionen die math-unit diesen Befehl enthält? Bei meinem steinzeitlichen Delphi5 gibt's denn nicht. :nixweiss:

Nachtrag 2: Eine verbesserte Variante siehe weiter unten.


Martok - Mi 04.07.12 22:38

Ich behaupte mal, as einzige was bei dir ernsthaft bremst ist die GUI-Aktualisierung über Application.ProcessMessages. Wie du siehst, ist mir sogar die Windows-Console zu langsam um das jeden Durchgang zu machen, deswegen das runterteilen auf jeden 100sten Durchlauf ;)

user profile iconMathematiker hat folgendes geschrieben Zum zitierten Posting springen:
Nachtrag: Ich habe mir ersteinmal FastMM4 besorgt, kannte ich natürlich nicht.
In neueren Delphis ist der sogar fest eingebaut. Bringt einiges, wenn man oft realloziieren muss (was SetLength ja tut).

user profile iconMathematiker hat folgendes geschrieben Zum zitierten Posting springen:
Beim Übersetzen Deines Textes fand mein Delphi
DivMod(Sum, NUMBER_BASE, Carry, Dig);
nicht.
Ich bin der Meinung die gibts schon länger, aber ich bin mir nicht sicher ob vielleicht in einer anderen Unit. Das Ding macht eigentlich nur Div und Mod in einem Durchgang und ist dabei angeblich schneller (ich kann mich an Messungen erinnern, die das relativiert haben), also:

Delphi-Quelltext
1:
2:
Carry:= Sum div NUMBER_BASE;
Dig:= Sum mod NUMBER_BASE;



EDIT:
ich lass das mal aus Spaß laufen:

Quelltext
1:
   1826000 it    756082 dig       20260.0 s                    


Horst_H - Do 05.07.12 10:10

Hallo,

hier wird doch nur addiert, da kann Carry nur 0 oder 1 sein.
Dadurch fällt auch die Variable Dig weg.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
  for p:= 0 to high(Summand) do 
    begin
    Sum:= Carry + Number[p] + Summand[p];
//  DivMod(Sum, NUMBER_BASE, Carry, Dig); ersatz   
    Carry := 0;
    IF  SUM >= NUMBER_BASE then
      begin
      dec(sum,NUMBER_BASE);      
      Carry := 1;
      end;
    Number[p]:= Sum;
  end;

Das macht einiges aus, ich habe mal die Zahl vor den Zahlenangeban und Zeiten ausgegeben
Startzahl ist 196 und wie man sieht: Die Zahlen ähneln sich ;-)
Original

Quelltext
1:
2:
3:
4:
5:
60112339269111851140389301806586681291351507449752782263142146284398312681631144
80513403906695460975689940797209307853656534423943491142030348495051500816735422
60147073619478325530666484993268957414968737533136622157990802952584506637311513

    241389 it    100000 dig    269.473066 s

Fälschung, erstatz von divMod

Quelltext
1:
2:
3:
4:
5:
60112339269111851140389301806586681291351507449752782263142146284398312681631144
80513403906695460975689940797209307853656534423943491142030348495051500816735422
60147073619478325530666484993268957414968737533136622157990802952584506637311513

    241389 it    100000 dig    167.871914 s


Es bringt sicher etwas.
Verbirgt sich hinter 241389/100000 eine neue mathematische Konstante :?:

Ich habe dummerweis 887 getestet, ach herrje, das ist doch 196+691 und braucht 241389-1 Itertionen für 100000 Stellen, vergeudete Abwärme...
Das setlength kann mann sicher reduzieren, durch indem man sich selbst die belegten Längen merkt und direkt 1 MB oder so belegt.
Ob sich das reversieren des Feldes lohnt wäre noch interessant. wenn meist schon nach drei Vergleichen feststeht es ist kein Palindrom könnte man mit einem Feld für die Zahl/Summe und und einem zusätzlichem halb so langem arbeiten.
Oberes Feld Zahl , unteres Feld Kopie der unteren Hälfte

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
Zahl/Sum  123456
Kopie     123 
ich summiere dann in die Zahl 
Zahl[1]=Zahl[1]+Zahl[6]
Zahl[2]=Zahl[2]+Zahl[5]
Zahl[3]=Zahl[3]+Zahl[4]
jetzt bei der Hälfte 
Zahl[4]=Zahl[4]+Kopie[3]
Zahl[5]=Zahl[5]+Kopie[2]
Zahl[6]=Zahl[6]+Kopie[1]

Man sieht die Nummerierung läuft einfach weiter.
Bei ungerader Zahllänge addiert man ja die mittlere Zahl auf sich selbst, dürfte also ohne Knoten im Hirn funktionieren.

Gruß Horst
Edit:
Ich habe mal die maximalen Vergleiche bis zur Erkennung eines Unterschiedes und bei welcher Iteration sie auftraten gestoppt:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
Startzahl: 196
Iteration   maximale Anzahl der Vergleiche auf Palindrom
         1   1
         6   2
        13   3
        16   4
        25   5
        70   8
       105  12
      1237  13
      1430  14
      3461  16
      3940  17
      9777  29
    429911  31
=
    483101 it    200000 dig 1.00391500000000E+003 s


Das Notebook ist doch wesentlich langsamer.
Ich hatte gehofft, der Ersatz der Addition und Kontrolle auf Carry durch den Einsatz einer Konstanten SumCarFeld beschleunigen zu können, hätte mehr gebracht.
Vielleicht liegt es an Freepascal und dessen Umgang mit dynaischen Feldern.
Ich habe auf dem Notebook nur Linux also habe ich nur Time aus sysutils genutzt, das hat eine Auflösung von 1 ms, das reicht mir allemal.
Sodele, schnell mal NumberAdd auf Pointerarithmetik umgestellt und die Befehle merkwürdig angeordnet( inc(pb1) read modify write möglichst zeitlich weit bis zum nächsten Zugriff )
Das spart etwa 50 % :-)


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:
{{$APPTYPE console}
program p196;
{$IFDEF FPC}
  {$MODE DELPHI}
  {$Optimization ON}
  {$Optimization RegVar}
  {$Optimization PEEPHOLE}
  {$Optimization CSE}
  {$Optimization ASMCSE}
{$Endif}

uses
  SysUtils;

const
  NUMBER_BASE = 10;
type
  TDigit = Byte;
  TSumCar = record
              s,c : byte;
            end;
  TSumCarFeld = array[0..19of TsumCar;          
  TNumber = array of TDigit;                                // Rückwärts! Niederwertigste Stelle ist [0]
const  
  SumCarFeld : TSumCarFeld =((s:0;c:0),(s:1;c:0),(s:2;c:0),(s:3;c:0),(s:4;c:0),
                             (s:5;c:0),(s:6;c:0),(s:7;c:0),(s:8;c:0),(s:9;c:0),
                             (s:0;c:1),(s:1;c:1),(s:2;c:1),(s:3;c:1),(s:4;c:1),
                             (s:5;c:1),(s:6;c:1),(s:7;c:1),(s:8;c:1),(s:9;c:1));
var
  MaxIndex : integer;
  Cycle: Cardinal;
  
procedure NumberFromString(var Number: TNumber; Str: string);
var
  i: integer;
begin
  SetLength(Number, Length(Str));
  for i:= 0 to Length(Str) - 1 do
    Number[i]:= Ord(Str[Length(Str) - i]) - Ord('0');
end;

function NumberToString(var Number: TNumber): string;
var
  i: integer;
begin
  SetLength(Result, Length(Number));
  for i:= 0 to High(Number) do
    Result[Length(Result) - i]:= Chr(Ord('0') + Number[i]);
end;

procedure NumberAdd(var Number, Summand: TNumber);
var
  pb1,pB2 : pByte;
  p: integer;
  Carry: integer;
begin
  Carry:= 0;
  pb1 := @Number[0]; 
  pb2 := @Summand[0]; 
  for p:=  high(Summand) downto 0 do 
    begin
//  DivMod(Sum, NUMBER_BASE, Carry, Dig);    
    With SumCarFeld[Carry + pb1^ + pb2^] do
      begin
      inc(pb2);
      pb1^ := s;
      inc(pb1);        
      Carry := c;
      end;
  end;
  
  IF Carry = 1 then
    begin
    p := Length(Summand)+1;
    setlength(Summand, p);
    SetLength(Number, p);
    Number[p-1] :=1;
    end;
end;

procedure NumberReverse(var Reversed, Number: TNumber);
var
  i: integer;
begin
  SetLength(Reversed, Length(Number));
  for i:= 0 to high(Number) do
    Reversed[high(Reversed) - i]:= Number[i];
end;

function NumberCompare(var A, B: TNumber): boolean;
begin
  Result:= (high(A) = high(B)) and
    CompareMem(@A[0], @B[0], Length(A));
end;

function NumberCompareB(var A: TNumber): boolean;
var 
  i,j: integer;
begin
  i := Low(A);
  j := High(A);
  repeat
    Result:= A[i]=A[j];
    inc(i);
    dec(j);
  until Not(Result) OR (J<I); 
  IF  MaxIndex <i then
    begin
     MaxIndex := i;
     writeln(cycle:10,i:4)
     end;
end;

var
  Work, Rev: TNumber;

  Time1, Time2 : TDateTime;
  s: string;
begin
  repeat
    Write('Startzahl: ');
    ReadLn(s);
    if s='' then
      break;
    NumberFromString(Work, s);
    Cycle:= 0// das erste ist kein cycle...
    Time1 := time;
    NumberReverse(Rev, Work);
    repeat
      NumberAdd(Work, Rev);
      NumberReverse(Rev, Work);
      inc(Cycle);
      //Keine Ausgabe !
      if Cycle AND cardinal(-1) = 0 then 
        begin
        Time2 := time;
        WriteLn(Cycle:10,' it',Length(Work):10,' dig',(Time2-Time1)*86400.0:14:1,' s');
        end;
    until NumberCompareB(REv) OR (Length(Work)>99999);
    Time2 := time;
    WriteLn('=');
    WriteLn(NumberToString(Work));
    WriteLn(Cycle:10,' it',Length(Work):10,' dig',(Time2-Time1)*86400.0,' s');

    WriteLn;
    WriteLn;
    WriteLn;
  until false;
end.
{
Startzahl: 196
         1   1
         6   2
        13   3
        16   4
        25   5
        70   8
       105  12
      1237  13
      1430  14
      3461  16
      3940  17
      9777  29
    429911  31
=
    483101 it    200000 dig 1.00391500000000E+003 s
NoteBook:
Vorher: NumberAdd
    241389 it    100000 dig 2.53229000000001E+002 s
Nachher: NumberAdd mit Pointerarithmetik
    241389 it    100000 dig 1.27149000000000E+002 s


Gammatester - Do 05.07.12 13:33

user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
Ich behaupte mal, as einzige was bei dir ernsthaft bremst ist die GUI-Aktualisierung über Application.ProcessMessages. Wie du siehst, ist mir sogar die Windows-Console zu langsam um das jeden Durchgang zu machen, deswegen das runterteilen auf jeden 100sten Durchlauf ;)
Was bei p196b sehr bremst, ist das völlig überflüssige Zusammenbasteln der Anzeige, auch dann wenn gar nichts angezeigt werden soll. Diese Bremse wird gelöst durch:

Delphi-Quelltext
1:
2:
3:
4:
5:
if checkbox1.checked then begin
  k:='';
  for i:=0 to laenge-1 do k:=chr(z[i]+48)+k;
  memo1.lines.add(inttostr(anz)+#9+k+' ('+inttostr(laenge)+')');
end;
statt

Delphi-Quelltext
1:
2:
3:
4:
k:='';
for i:=0 to laenge-1 do k:=chr(z[i]+48)+k;
if checkbox1.checked then
  memo1.lines.add(inttostr(anz)+#9+k+' ('+inttostr(laenge)+')');

Das Result für die 10000 Schritte auf meinem Rechner: Statt 146s mit dem Original braucht die (D6 kompilierte) neue Version nur noch ca 4s (handgestoppt).


Mathematiker - Do 05.07.12 13:59

user profile iconGammatester hat folgendes geschrieben Zum zitierten Posting springen:
Was bei p196b sehr bremst, ist das völlig überflüssige Zusammenbasteln der Anzeige, auch dann wenn gar nichts angezeigt werden soll.

Gerade, als ich meine neue Variante senden wollte, kam Deine Nachricht. Genau das(!) bremst ungemein.
Außerdem habe ich auch div und mod "rausgeworfen". Da die Summe zweier Ziffern nicht größer als 19 wird, genügt die Subtraktion mit 10 und das Inkrementieren der nächsten Ziffer.
Auf meinem Rechner brauche ich nun für 100000 Ziffern Länge nur noch 207 Sekunden.
Beste Grüße
Mathematiker

Nachtrag: Statt e[i]:=e[i]-10 nun dec(e[i],10) bringt noch einmal 4 Sekunden. Nicht viel, aber ein Anfang. :)
^Horst_H: Pointerarithmetik muss ich erst einmal verstehen.


Martok - Do 05.07.12 16:00

Ich hab gestern auch noch etwas gebastelt und auch nochmal gut 10% rausgeholt, aber grade, als ich das hochladen wollte, das gesehen [http://www.p196.org/screens.html].
100k Iterationen in 5-10 Sekunden, irgendwas machen wir prinzipiell falsch.

Das Compare ist nicht das teuere: MemCmp bricht bei Ungleicheheit sowieso ab, d.h. nur ein Palindrom wird überhaupt in die 2. Hälfte kommen. Und dass da bis 413mio Iterationen keins kommt wissen wir schon [http://www.p196.org/milestones.html] ;) Und da die Reverse eh vorhanden ist, kann man auch gleich das nehmen: mit FastMM vergleicht der in Machine Word Schritten.

Horst_H: ist eine Tabelle da echt schneller als Add/Cmp/Sub? Guck an.

Wenn wir eh Pointer nehmen, kann auch das dynamische Array raus.

Ich häng mal den Stand von gestern Abend an; mittlerweile ganz ohne Carry, und nur noch Inc/Dec/Cmp in einer Schleife.
Ca 51s für 100k Iterationen.


Mathematiker - Do 05.07.12 17:03

user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
Ich häng mal den Stand von gestern Abend an; mittlerweile ganz ohne Carry, und nur noch Inc/Dec/Cmp in einer Schleife.Ca 51s für 100k Iterationen.

Sehr schön, das ist das Schnellste bisher.

user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
100k Iterationen in 5-10 Sekunden, irgendwas machen wir prinzipiell falsch.

Du hast recht, aber was ist prinzipiell falsch. Scheinbar gibt es eine völlig andere Grundidee zur Bestimmung der Folgenglieder. Aber welche? Irgendwie rätselhaft. :nut:
Da ich von C# keinerlei Ahnung habe, meine Frage: Würde Dein Programm mit C# eigentlich deutlich schneller laufen?
Beste Grüße
Mathematiker


Horst_H - Do 05.07.12 17:38

Hallo,

@Martok:
Die Interseite lässt die Hoffnung schwinden oder sind es die 27 Grad im Zimmer ;-)
Eine kleine Anmerkung. Dein Programm berechnet 100000 Durchgänge und ich habe oben 100000 Stellen gerechnet, also 241xxx Durchgänge.

Delphi-Quelltext
1:
2:
3:
4:
      if Cycle >= 100000 then
        break;
statt
until NumberCompareB(REv)or (Length(Work)>99999);

Bitte erst die Zahl ausgeben und dann die Zeiten, ich habe sie nicht mehr im Fenster.
Eine Kompilierung mit Freepascal 2.6.0 kam auf 22 Sekunden, meine Version auf 16 Sekunden, obwohl CompareMem etwas schneller wäre, aber es ist mehr eine Findungsphese, wie man das implementieren könnte
Ich bastel an der Version ohne ReverseFeld und einem Kopiefeld von halber Größe, wie oben beschrieben.

Vielleicht kann man zwei BCD_Ziffern in einem Byte verarbeiten, Ein SumCarFeld hätte auch nur 199 Einträge, aber dann muss man sehr oft die obere Hälfte um 4 Bit schieben.

@Mathematiker: bin immer noch schneller, aber weit weit weg vom Optimum,
Diese p196 Laufzeiten [http://www.p196.org/software_comparisons.html] sondern einen mehr erstaunen.
Die Laufzeit wächst ja theoretisch quadratisch mit der Ziffernzahl.
Ich brauch 95 Sekunden für 100000 Stellen = 241??? Durchläufe, Hochgerechnet sind das 606 Sekunden also 10 min 6 Sekunden.
Aber wir kennen den Rechner der Internetseite nicht.
51 Sekunden sind schon sehr beeindruckend.
Zitat:
oder's name and Location 0 - 603,567 Iterations
Vaughn Suite - Trinidad 0:51



Gruß Horst
EDIT:
Laufzeit: 592 Sekunden sind 9min52 Sekunden

Quelltext
1:
2:
3:
....086914228293595882210473480636969512153155697087

    603567 it    250000 dig 5.92248000000005E+002 s


Mathematiker - Do 05.07.12 18:04

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
@Mathematiker: bin immer noch schneller, aber weit weit weg vom Optimum,

Tut mir leid. :flehan: Du hast recht, Dein Programm ist im Moment hier das schnellste.
Beste Grüße
Mathematiker


FinnO - Do 05.07.12 18:09

Moin,

mal so nebenbei - am Ende des Tages, was hat man da erreicht? ;)

LG


Mathematiker - Do 05.07.12 19:04

user profile iconFinnO hat folgendes geschrieben Zum zitierten Posting springen:
mal so nebenbei - am Ende des Tages, was hat man da erreicht? ;)

"Der Mathematiker studiert die reine Mathematik nicht, weil sie nützlich ist; er studiert sie, weil er sich an ihr erfreut, und er erfreut sich an ihr, weil sie schön ist." Henri Poincaré
"Mathematik allein befriedigt den Geist durch ihre außerordentliche Gewissheit." Johannes Kepler
"Die Mathematik ist eine Art Spielzeug, welches die Natur uns zuwarf zum Troste und zur Unterhaltung in der Finsternis." Jean-Baptist le Rond d'Alembert

Beste Grüße
Mathematiker


Delphi-Laie - Do 05.07.12 19:43

user profile iconFinnO hat folgendes geschrieben Zum zitierten Posting springen:
Moin,

mal so nebenbei - am Ende des Tages, was hat man da erreicht? ;)

LG


Kann da nur für mich sprechen: Zufriedenheit, Befriedigung, ja sogar ein gewisses Genuß- bis Glücksgefühl: Wenn das Ergebnis eigener Mühen, das Compilat, endlich klappt und genau das tut, was man möchte. Meine Wenigkeit z.B. programmiert nicht per se gern, sondern ergebnisorientiert. Insofern ist Programmierung für mich nur Mittel zu Zweck, und die Programmierung sogar eine zeit- und konzentrationsraubende Lästigkeit auf dem Wege zum Ziel. Insofern erfreue ich mich sehr an den hier vorgestellten Problemen und weiß den Computer als Problemlösungshilfe durchaus zu schätzen (neuerdings wird er sogar als Beweishilfsmittel eingesetzt, aber das mögen viele Mathematiker aus gutem Grunde nicht).


Horst_H - Do 05.07.12 20:04

Hallo,

das Gehirn bleibt nur frisch, wenn es ab und was neues zu tuen bekommt.Dafür sind solche sinnfreien Sachen doch ideal.

Gruß Horst
@Mathematiker:
:flehan: erst wenn es schneller als 51 Sekunden ist ;-)
Aus diesem Rechner natürlich:
Zitat:
The following is a brief comparison of the fastest applications. The test machine is a 2.8GHz Pentium IV (Hyper-threading enabled) with an 800MHz FSB, over-clocked to 2.95GHz, with (2) - 512Mb, 400MHz (PC3200) DDR SDRAM modules (1GB total), 40GB hard drive, running Windows XP pro.


Martok - Do 05.07.12 20:45

Hyperthreading ist auch noch sowas - ich hab keinen Schimmer, wo wir hier parallelisieren sollten.

Wollen wir uns als Geschwindigkeitstest auf 20k Stellen einigen? Dann muss man nicht immer überlegen, welches Programm grade was ausgibt und es ist wenig genug, dass ich das auch in den Läufen mit GPProfiler machen kann ;)

Ja, die Laufzeit sollte quadratisch sein. Laut MathWorld ist O(k^2) für k Iterationen die optimale Implementation.

Code gibts ab jetzt auf Github [https://github.com/martok/experiment-196], wer dort ist kann ja forken ;)

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Bitte erst die Zahl ausgeben und dann die Zeiten, ich habe sie nicht mehr im Fenster.
Habs grad geändert, die ersten und letzten 25 Stellen scheinen da gerne verwendet zu werden.

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Vielleicht kann man zwei BCD_Ziffern in einem Byte verarbeiten, Ein SumCarFeld hätte auch nur 199 Einträge, aber dann muss man sehr oft die obere Hälfte um 4 Bit schieben.
Siehe Code, ich hab die Carries mal komplett rausgeworfen. Ist geringfügig schneller, aber bringt nicht allzu viel (ungefähr 1 Sekunde auf 100k Iterationen).
Ich werde mal die Schleife in Asm schreiben, der Delphi-Compiler gefällt mir da nicht so ganz.


Quelltext
1:
2:
3:
=
    241389 it    100000 dig    293.801473 s
1316113735615475349297100 ... 5799080295258450663731151


Horst_H - Do 05.07.12 21:24

Hallo,

ich bin gerade bei 100k Stellen in 52 Sekunden, statt 95 ist schon was :-)
Natürlich läßt sich das parallelisieren.
Eine CPU das 1. Viertel + 4. , zweite 2. mit 3. Viertel
An den Schnittpunkten Viertel 1 auf 2 und Viertel 3 auf 4 Carry einpflegen, selbst pi hat lange Zeiten maximal 6-fach "9" hintereinander.Oh, gerade Fertig:

Quelltext
1:
    603567 it    250000 dig 3.23028000000001E+002 s                    

Merkwürdig:
bei 100 k vorher 95/ jetzt 52 Sekunden und bei 250k 592/323 Sekunden das Verhältnis ist identisch.
Eine kleine Anzahl an Digits reicht also.
NumberRevers habe ich gestrichen, findet jetzt hier statt.
Ein Pointer von End zum Anfang, der andere umgekehrt.
In der Mitte wird das Feld gewechselt

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:
procedure NumberAdd_1Feld(var Number: TNumber);
var
  pb1,pB2 : pByte;
  p: integer;
  Carry: integer;
begin
  // LenghtNum und LengthRev sind globale Variable, wie die Felder auch
  // MIst, die Übergabe hätte ich mir sparen können
  pb2 := @Number[LengthNum-1];
  pb1 := @Number[0]; 
  Move(Number[0],Rev[0],LengthRev);
  Carry:= 0;

  for p:=  LengthRev-1  downto 0 do 
    begin
    With SumCarFeld[Carry + pb1^ + pb2^] do
      begin
      dec(pb2);
      pb1^ := s;
      inc(pb1);        
      Carry := c;
      end;
  end;

  //mittlere Zahl auf sich selbst addieren
  IF ODD(LengthNum) then
    begin
    With SumCarFeld[Carry + pb1^ + pb1^] do
      begin
      pb1^ := s;
      inc(pb1);        
      Carry := c;
      end;
    end;

  pb2 := @rev[LengthRev-1];
  for p:=  LengthRev-1  downto 0 do 
    begin
    With SumCarFeld[Carry + pb1^ + pb2^] do
      begin
 //     writeln(pb1^:3,'+',pb2^:3,'+',c);      
      dec(pb2);
      pb1^ := s;
//  writeln('Num ',NumberToString  (Number));

      inc(pb1);        
      Carry := c;
      end;
  end;
  //Korrektur der Laenge
 IF Carry = 1 then
    begin
    inc(LengthNum);
    SetLength(Number, LengthNum);
    Number[LengthNum-1] :=1;
    IF NOT(ODD(LengthNum)) then
       begin
       inc(LengthRev);
       SetLength(Rev, LengthRev);
       end;
    end;
end;


Nur noch ein Faktor 7 bis 10.
Jetzt probiere ich mal statische Felder.

Gruß Horst


Mathematiker - Do 05.07.12 22:14

Hallo,
es freut mich, dass ich einen "kleinen Wettbewerb" bewirkt habe.

An die Zeiten von Horst_H komme ich mit meinen einfachen Programmierfähigkeiten nicht heran. Es ist mir aber gelungen, mein Programm weiter zu beschleunigen. Beim einem Austausch der Button1Click-Methode mit

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:
procedure TForm1.Button1Click(Sender: TObject);
const zmax:integer=999;
var k:string;
    i,anz,laenge:integer;
    gefunden:boolean;
    z,e:array of byte;
    Time1, Time2, Freq: Int64;
begin
    if abbruch=false then begin abbruch:=true; exit end;
    button1.caption:='Abbruch';
    label3.caption:='';
    abbruch:=false;
    memo1.clear;
    application.processmessages;

    zmax:=strtoint(edit2.text);
    if zmax<100 then zmax:=100;

    setlength(z,zmax+2);
    setlength(e,zmax+2);
    for i:=0 to zmax do begin z[i]:=0; e[i]:=0 end;
    k:=edit1.text;
    if k='' then k:='196';
    laenge:=length(k);
    for i:=laenge downto 1 do z[laenge-i]:=ord(k[i])-48;
    anz:=0;

    QueryPerformanceFrequency(Freq);
    QueryPerformanceCounter(Time1);

    repeat
      inc(anz);
      if checkbox1.checked then begin
        k:='';
        if laenge>50 then
        begin
          for i:=0 to 25 do k:=chr(z[i]+48)+k;
          k:=' ... '+k;
          for i:=laenge-26 to laenge-1 do k:=chr(z[i]+48)+k;
        end
        else
          for i:=0 to laenge-1 do k:=chr(z[i]+48)+k;
        memo1.lines.add(inttostr(anz)+#9+k+' ('+inttostr(laenge)+')');
      end;

      for i:=0 to laenge-1 do
        e[i]:=z[i]+z[laenge-1-i];
      for i:=0 to laenge-1 do begin
        if e[i]>9 then begin
          inc(e[i+1]);
          dec(e[i],10);
        end;
      end;
      if e[laenge]>0 then inc(laenge);

      gefunden:=true;
      i:=0;
      repeat
        if e[i]<>e[laenge-1-i] then gefunden:=false;
        inc(i);
      until (not gefunden) or (i>laenge div 2);

      if gefunden then
      begin
        memo1.lines.add('Palindrom gefunden im Schritt '+inttostr(anz));
        k:='';
        for i:=0 to laenge-1 do k:=chr(e[i]+48)+k;
        memo1.lines.add(k);
      end
      else
        z:=copy(e,0,laenge);

      if anz mod 1000 = 0 then begin
        label4.caption:=inttostr(laenge);
        QueryPerformanceCounter(Time2);
        label6.caption:=inttostr(anz)+' Zyklen | '+floattostrf((Time2-Time1)/freq,ffgeneral,10,6)+' s';
        application.processmessages;
      end;
    until abbruch or gefunden or (laenge>zmax);

    label4.caption:=inttostr(laenge);
    QueryPerformanceCounter(Time2);
    label6.caption:=inttostr(anz)+' Zyklen | '+floattostrf((Time2-Time1)/freq,ffgeneral,10,6)+' s';
    if abbruch or (laenge>zmax) then
    begin
      memo1.lines.add('Folgenglied im Schritt '+inttostr(anz));
      memo1.lines.add('Länge '+inttostr(laenge));
      k:='';
      for i:=0 to 25 do k:=chr(z[i]+48)+k;
      k:=' ... '+k;
      for i:=laenge-26 to laenge-1 do k:=chr(z[i]+48)+k;
      memo1.lines.add(k);
    end;
    button1.caption:='Berechnung';
    abbruch:=true;
    setlength(z,0);
    setlength(e,0);
end;

ist die Hauptänderung das Kopieren der Summe in die Zahl: z:=copy(e,0,laenge);
Dies bringt (für mich) erstaunlich viel. Mein Rechner braucht für 100k Ziffernlänge, also 241xxx Iterationen, nur noch 184 Sekunden. Natürlich nicht zu vergleichen mit den 52 Sekunden von Horst_H.
Ein bisschen bin ich trotzdem stolz. :)

@Horst_H
Ganz so sinnlos ist die Suche nach dem 196er-Palindrom nicht. Sollte sich herausstellen, dass es dies nicht gibt (was natürlich eine Rechnung nicht beweisen kann), sind die Zahlentheoretiker gefragt, die dann nach der(!) Ursache für das merkwürdige Verhalten der 196 suchen müssen. Im Ergebnis gibt es dann viele Abhandlungen mit vielen neuen Sätzen und Beweisen, manchmal sogar eine ganz neue Theorie. Findest Du ein Palindrom, ersparst Du den Theoretikern eine Menge Arbeit. :wink:

Beste Grüße
Mathematiker


Martok - Do 05.07.12 22:41

:gruebel: Okay, dein Add/Reverse in einem versteh ich nicht.

Dafür hab ich nochmal 120% rausgeholt durch viel Assembler [https://github.com/martok/experiment-196/commit/61eec6c88305282b8ee6ed9c28c0e9d0c3d56de6]. Dieser Ansatz ist damit aber quasi am Ende, jetzt geht nur noch CALLs einsparen durch Inlining oder halt das Kombinierte. Da muss ich aber nochmal drüber nachdenken ;)


Quelltext
1:
2:
3:
4:
5:
     48150 it     20000 dig      4.571300 s
1991208502591586282902869 ... 0206730937179519421471319

    241389 it    100000 dig    117.477378 s
1316113735615475349297100 ... 5799080295258450663731151


Mathematiker - Do 05.07.12 22:54

Hallo Martok,
ich staune immer wieder, was Assembler bringt, und bin beeindruckt. Wenn ich es nur könnte!
Eine Kleinigkeit:
user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:

Quelltext
1:
2:
3:
4:
5:
     48150 it     20000 dig      4.571300 s
1991208502591586282902869 ... 0206730937179519421471319

    241389 it    100000 dig    117.477378 s
1316113735615475349297100 ... 5799080295258450663731151

Meiner Meinung nach fehlt bei der Zahlausgabe die letzte Stelle. Müsste es nicht
1316113735615475349297100 ... 57990802952584506637311513
sein?
Beste Grüße
Mathematiker


Martok - Do 05.07.12 23:16

Ja, du hast Recht, es muss natürlich Copy(s, length(s) - 2425); lauten, nicht Copy(s, length(s) - 2525);.

Sinnloseste Optimierung des Tages: bringt knapp 0.1s [https://github.com/martok/experiment-196/commit/0e7eb25acdab08ba23683985ec9b01ce9a20033b]...

Also ich würde auch lieber was sinnvolles tun als den Assembler spielen, aber Delphi fehlt ja allein schon die RegisterVariables-Optimierung... viel mehr hab ich gar nicht gemacht.

EDIT: Habs verstanden, was user profile iconHorst_H da tut, und als ASM eingebaut:

Quelltext
1:
2:
    241389 it    100000 dig     88.742270 s
1316113735615475349297100 ... 7990802952584506637311513

Damit sind wir auf eine Größenordnung ran, ein mir bekannter Physiker sagte mal, das reicht :lol:
Hab auch mal die Zeiten geplottet, dass ist jetzt exakt quadratisch. Mal die Million Stellen vollmachen.


Horst_H - Fr 06.07.12 08:55

Hallo,


Danke für den Tipp, dass man das Feld nicht halb umkopieren muss.
Kommutativ a+b= b+a ...
Du speicherst Du Summe direkt an Stelle von a und an der Stelle von b

Delphi-Quelltext
1:
2:
3:
4:
procedure NumberAddReversed(var Number: TNumber); 
...
    mov byte ptr [ecx], al            // speichern in number
    mov byte ptr [edx], al            // ...

Aber dann muss ich den Übertrag auch in einem zweiten Schritt bearbeiten.
Und diese nicht vorhersagbaren Sprünge kosten doch enorm Zeit ( bei mir 20 Takte (AMD Phenom II 3,2 Ghz ) ), deshalb bringt die Tabelle bei mir etwas, weil ich nur eine Schleife ( leicht vorhersagbar ) ohne Sprünge darin habe.
Wenn Du ohnehin die Überträge später bearbeiten willst, kann man dann nicht auch BSWAP benutzen?
Dann addierst Du, solange es geht in 32-Bit.
Dazu brauchst aber ein paar mehr Register.
Aber mir ist immer noch nicht klar, wie ich einen Faktor 8 in der Geschwindgikeit einbauen könnte.

Gruß Horst


Mathematiker - Fr 06.07.12 09:46

Hallo Martok und Horst_H,
dass ich mit einfachen Feldern und ohne Assembler nicht an Eure schnellen Programme herankomme, ist mir klar.
Nun dachte ich, wenigstens die Idee eines Consolen-Programms zu nutzen. Mein Versuch:

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:
{$APPTYPE console}
program p196console;

uses
  SysUtils,
  Windows;

const zmax:integer=999;
var k:string;
    i,anz,laenge,laenge1:integer;
    gefunden:boolean;
    z,e:array of byte;
    Time1, Time2, Freq: Int64;
begin
  repeat
    write('Startzahl: ');
    readln(k);
    if k='' then k:='196';
    laenge:=length(k);
    write('maximale Laenge: ');
    readln(zmax);
    if zmax<100 then zmax:=100;
    writeln('');

    setlength(z,zmax+2);
    setlength(e,zmax+2);
    for i:=0 to zmax do begin z[i]:=0; e[i]:=0 end;
    for i:=laenge downto 1 do begin
      z[laenge-i]:=ord(k[i])-48;
      e[laenge-i]:=ord(k[i])-48;
    end;
    anz:=0;
    laenge1:=laenge-1;

    QueryPerformanceFrequency(Freq);
    QueryPerformanceCounter(Time1);

    repeat
      inc(anz);
      for i:=0 to laenge1 do inc(e[i],z[laenge1-i]);
      for i:=0 to laenge1 do begin
        if e[i]>9 then begin
          inc(e[i+1]);
          dec(e[i],10);
        end;
      end;
      if e[laenge]>0 then inc(laenge);
      laenge1:=laenge-1;

      gefunden:=true;
      i:=0;
      repeat
        if e[i]<>e[laenge1-i] then gefunden:=false;
        inc(i);
      until (not gefunden) or (i>laenge div 2);

      if gefunden then
      begin
        writeln('Palindrom im Schritt '+inttostr(anz));
        k:='';
        for i:=0 to laenge1 do k:=chr(e[i]+48)+k;
        writeln(k);
      end
      else
        z:=copy(e,0,laenge);

      if anz mod 2000 = 0 then begin
        QueryPerformanceCounter(Time2);
        writeln(anz:10,' It ',(Time2-Time1)/freq:10:6,' s');
      end;
    until gefunden or (laenge=zmax);

    QueryPerformanceCounter(Time2);
    writeln(anz:10,' It ',(Time2-Time1)/freq:10:6,' s');
    if (laenge=zmax) then
    begin
      writeln('Folgenglied im Schritt '+inttostr(anz));
      writeln('Laenge '+inttostr(laenge));
      k:='';
      for i:=0 to 25 do k:=chr(z[i]+48)+k;
      k:=' ... '+k;
      for i:=laenge-26 to laenge-1 do k:=chr(z[i]+48)+k;
      writeln(k);
    end;
    setlength(z,0);
    setlength(e,0);
    writeln('');
  until false;
end.

bringt mir aber nur 3 Sekunden Gewinn, statt 174 Sekunden bei 100k Stellen, mit "schönem" Formular usw., nun 171 Sekunden.
Ist der Unterschied wirklich so gering oder was mache ich falsch?
Beste Grüße
Mathematiker


Gammatester - Fr 06.07.12 09:50

Moin, falls es noch jemanden interessiert: Im Anhang eine PurePascal-Implemenation von gestern abend (hab's eben noch auf dyn. Arrays umgestellt), mit

Quelltext
1:
2:
1316113735615475349297100 ... 7990802952584506637311513
    241389 it    100000 dig     71.290302 s
Mit ASM-Optimierung sollte also noch etwas herauszuholen sein. Hauptfeatures: Wieder einführen des Carry, dafür kein Umkopieren/Reverse (deshalb werden die Iteration immer paarweise gemacht).

Gruß Gammatester


Horst_H - Fr 06.07.12 11:44

Hallo,

Dein Kompilat erreicht bei mir:

Quelltext
1:
2:
1316113735615475349297100 ... 7990802952584506637311513
    241389 it    100000 dig     75.367305 s

Mit FPC2.6.0 kompiliert:

Quelltext
1:
2:
1316113735615475349297100 ... 7990802952584506637311513
    241389 it    100000 dig     89.618406 s

Mit meinem SumCarryFeld wird es wieder schnell.

Quelltext
1:
2:
1316113735615475349297100 ... 7990802952584506637311513
    241389 it    100000 dig     52.276415 s

Meine ganze Pointerei zuvor hat sich also nicht gelohnt, auch das noch eine reine Pascal Ausarbeitung.

Ob Byte oder word gab bei mir keinen Laufzeitunterschied.
Freepascal mag diese dynamischen Felder nicht besonders, zwei Befehle um die Adreesse des Feldes heraus zu bekommen, wobei diese auf dem Stack liegt und das innerhalb der Schleife 2-fach jedesmal ...

Gruß Horst


Gammatester - Fr 06.07.12 12:20

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Mit meinem SumCarryFeld wird es wieder schnell.

Quelltext
1:
2:
1316113735615475349297100 ... 7990802952584506637311513
    241389 it    100000 dig     52.276415 s

...
Freepascal mag diese dynamischen Felder nicht besonders

In der Tat :zustimm: Sehr schnell Deine Modifikation :!: Mit Delphi hat man jetzt schon brauchbare Zeiten, auf meinem Rechner:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
FPC2.6.0
=
1316113735615475349297100 ... 7990802952584506637311513
    241389 it    100000 dig     54.138380 s

Delphi 12
=
1316113735615475349297100 ... 7990802952584506637311513
    241389 it    100000 dig     35.098787 s


jfheins - Fr 06.07.12 13:44

Hi,
ich habe das ganze mal in C# umgesetzt :-)

Ich erreiche bislang folgende Werte:
Zitat:
241390 Iterationen in 62,35 Sekunden.
Die Zahl hat jetzt 100000 Stellen.


Ich habe leider schon Pointer einsetzen müssen (-20% Laufzeit) um darauf zu kommen. Zudem wird im Moment nicht geprüft ob ein Palindrom vorhanden ist :angel:
Außerdem habe ich eine MultiThreading Variante gebaut, aber die ist deutlich langsamer - Laufzeit +160%
Ich habe hier aber nicht ganz den Überblick, was ihr schon alles Optimiert habt; das SumCarryFeld hat bei mir keinen Vorteil gebracht. (Array in C# sind wohl nicht so perfomant) könnte ich aber nochmal mit Pointern probieren.
Edit:
Okay, mit dem SumCarryFeld und Pointerzugriff darauf bin ich noch etwas schneller:
Zitat:
241390 Iterationen in 31,09 Sekunden.
Die Zahl hat jetzt 100000 Stellen.

Ich baue dann heute Abend mal die Palindromüberprüfung ein, dann wird das vergleichbar :)


Martok - Fr 06.07.12 16:14

Kurze Durchsage zwischendurch: die Programme an sich funktionieren:

Quelltext
1:
2:
3:
4:
5:
   2415600 it    999900 dig       10519.0 s
   2415800 it    999983 dig       10520.5 s
=
   2415836 it   1000000 dig  10520.815838 s
1321620866345900792403087 ... 1880303207999544559027122

Richtige Stellenzahl nach richtigen Iterationen mit richtigen ersten 25 (Vgl. [http://www.p196.org/verification.html])


Das von user profile iconGammatester ist bei mir wesentlich langsamer, was mach ich falsch?

Quelltext
1:
2:
1316113735615475349297100 ... 7990802952584506637311513
    241389 it    100000 dig    135.658393 s

Die Feldzugriffe sind allerdings tödlich, ja. So als Record hatte ich das auch mal kurz, das ist nicht wirklich gut. Denke damit das was wird muss man ein statisches Array hinten rein hängen, und dann den ganzen Record per ReallocMem selber verwalten:

Delphi-Quelltext
1:
2:
3:
4:
  TNumber = packed record
    Length: integer;
    Digits: array[0..0of TDigit;
  end;


user profile iconjfheins: endlich mal jemand mit einem Compiler, der ordentlichen Code erzeugt, und wenns nur JIT ist :)

EDIT: 2 Sekunden geholt, indem ich die Carries nicht mehr einzeln nachführe sondern gleich mit behandle. Und eine echte Überraschung: ein Tabellen-Lookup ist langsamer als echtes CMP/SUB. Hätte ich nicht erwartet.

EDIT2: what

Das sind 20 Sekunden ggü. meinem letzten
1:
2:
    241389 it    100000 dig     67.761056 s
1316113735615475349297100 ... 7990802952584506637311513


Mathematiker - Fr 06.07.12 20:11

Hallo,
das SumCarryFeld von Horst_H ist genial. Selbst bei meinem Programm und meiner "alten Mühle" konnte ich die Zeit fast halbieren. Danke.

Quelltext
1:
2:
    241389 it    100000 dig          82.5 s
13161137356154753492971008 ... 57990802952584506637311513

Beste Grüße
Mathematiker


jfheins - Fr 06.07.12 20:58

Okay, also hier mal meine exe Datei.

Er schafft jetzt auf meinem Rechner:
Zitat:
241389 Iterationen in 31,21 Sekunden.
Die Zahl hat 100000 Stellen.
Palindrom? False
131611373561547534929...02952584506637311513


Es wird auf Palindrome geprüft (aber nicht abgebrochen) und nach dem Cancel-Button wird die geplante Anzahl an Iterationen ausgegeben.
Sonst geht's eigentlich schon ganz gut :-)


Horst_H - Fr 06.07.12 21:07

Hallo,

mal eine ganz andere Überlegung, ob es zum Palindrom kommt oder nicht.
Gäbe es keine Überträge bei Addition hat man immer ein Palindrom.
Kein Carry vorgekommen -> Palindrom
Carry vorgekommen -> kein Palindrom, denn Carry wirkt in eine Richtung und ist damit unsymmetrisch.Muesste man mal testen, indem man die Summe der Carry erzeugt.
Also bestimmt man eigentlich die Wahrscheinlichkeit, dass alle Stelle[i]+Stelle[Laenge-i] in der Summe unter der Basis bleiben.
Mit zunehmender Stellenzahl muss die doch verschwindet gering sein.

Ist es nicht auch erstaunlich, dass es 2,41... Iterationen braucht, bis zu einer neue Stelle kommt.
Die mittlere Summe zweier zufälliger Ziffern zur Basis B ist doch (B-1), also müsste nach (1+1/Basis) eine neue Ziffern kommen, aber die Neue Ziffer ist immer der Übertrag und damit 1.
Im Dezimalsystem ist das 1,1. Die mittlere Ziffer ist (Basis-1)/2 = 4.5
10 = 2,599...^2,41, das hieße die mittlere Ziffer in der ersten und letzten Stelle wäre 2,6
Zu Beginn ist die oberste Ziffer 1 und die unterste <= der zweitobersten Stelle ( Carry wandert ja nur nach oben ) also zwischen 0 und 8, nie die 9.
1.Iteration 1+ (0..8 ) ergibt im Schnitt 5 bleibt immer kleiner Basis
2.Iteration Müsste doch jetzt im Mittel einen Überlauf erzeugen, tut es aber nicht.
Das kann der Mathematiker sicher erklären, er hat ja jetzt etwas mehr Zeit, weil das Programm schneller ist ;-)

Gruß Horst
P.S.
Was habt jheins für einen schnellen Rechner??p196_Net


Mathematiker - Fr 06.07.12 21:49

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Gäbe es keine Überträge bei Addition hat man immer ein Palindrom.
Kein Carry vorgekommen -> Palindrom
Carry vorgekommen -> kein Palindrom, denn Carry wirkt in eine Richtung und ist damit unsymmetrisch.

Das ist mit größter Wahrscheinlichkeit so. Einen Beweis kann ich aber nicht liefern. Leider.
user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Was habt jheins für einen schnellen Rechner??

Das möchte ich auch wissen. Bei mir braucht sein Programm 99,29 Sekunden für 100k Stellen.
Übrigens könnt Ihr alle stolz sein. "Mathematica" brauchte bei einem Test 10,6 Stunden für 250000 Iterationen.
http://mathworld.wolfram.com/196-Algorithm.html :zwinker:
Das war zwar schon vor 10 Jahren auf einem 450 MHz Pentium II, aber wer kann schon von sich behaupten besser als "Mathematica" zu sein.
Man kann ja so rechnen: Mein PC 1,7 GHz ist 3,8 mal schneller als der genannte Test-PC. D.h., ich muss nach 2 Stunden 48 min 250000 Iterationen haben. Und somit sind wir alle mindestens 100mal schneller als "Mathematica". :beer:

@jheins: Wie schaffst Du es, dass Dein Programm mit Windows-Oberfläche nur 15 k groß ist?

Beste Grüße
Mathematiker

Nachtrag: "Carry vorgekommen -> kein Palindrom" stimmt doch nicht. Beispiel: 999222 wird zum Palindrom 1222221.


Martok - Fr 06.07.12 21:59

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
mal eine ganz andere Überlegung, ob es zum Palindrom kommt oder nicht.
Gäbe es keine Überträge bei Addition hat man immer ein Palindrom.
Kein Carry vorgekommen -> Palindrom
Carry vorgekommen -> kein Palindrom, denn Carry wirkt in eine Richtung und ist damit unsymmetrisch.Muesste man mal testen, indem man die Summe der Carry erzeugt.
Also bestimmt man eigentlich die Wahrscheinlichkeit, dass alle Stelle[i]+Stelle[Laenge-i] in der Summe unter der Basis bleiben.
Mit zunehmender Stellenzahl muss die doch verschwindet gering sein.

Hihi. [http://www.p196.org/papers5.html]

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Übrigens könnt Ihr alle stolz sein. "Mathematica" brauchte bei einem Test 10,6 Stunden für 250000 Iterationen.
Interessant wäre ja das Programm, um das heute mal laufen zu lassen...

user profile iconMathematiker hat folgendes geschrieben Zum zitierten Posting springen:
@jheins: Wie schaffst Du es, dass Dein Programm mit Windows-Oberfläche nur 15 k groß ist?
.NET. Die restlichen 250MB sind im Framework.


Aktueller Stand: 62s für 100k Stellen, nach 5550s 922884 Stellen. Laut AMD CodeAnalyst das langsamste:

uNumbers.pas
 
217:
218:
219:
220:
{ ... }
asm
        mov al, byte ptr [edx]           //wert
        mov byte ptr [ecx], al
end;
Klar - read-modify-write, gleich zweimal. Ein mov m8,m8 wäre da echt praktisch. Ist leider der Carry-Durchlauf, da brauch ich die einzeln...


Horst_H - Fr 06.07.12 22:39

Hallo,

dann hat sich das mit der Summe der Carry erledigt.
Aber ist Dir im dem Text http://www.p196.org/files/kruppa.txt aufgefallen, das Summe der zu addierenden Ziffern immer 11 oder 0 ist. Aber auch das zu testen ist Kokolores.

Nein , ich benutze kein mathematica, das ist was für Mathematiker.

Gruß Horst


Mathematiker - Fr 06.07.12 22:45

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Nein , ich benutze kein mathematica, das ist was für Mathematiker.

Aber nicht für "arme" Mathematiker. 3185 € für die Standard-Version von Mathematica ist mir einfach zu "billig". :lol:
Beste Grüße
Mathematiker


jfheins - Sa 07.07.12 00:50

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:

[...]
Was habt jheins für einen schnellen Rechner??


user profile iconMathematiker hat folgendes geschrieben Zum zitierten Posting springen:

[...]
Das möchte ich auch wissen. Bei mir braucht sein Programm 99,29 Sekunden für 100k Stellen.
[...]
@jheins: Wie schaffst Du es, dass Dein Programm mit Windows-Oberfläche nur 15 k groß ist?


Hallo,
ich habe einen Intel Q6600 aus dem Jahr 2009, übertaktet auf 3 GHz. Also ohne TurboBoost und eigentlich nicht besonders schnell. Dachte ich.
Da hatte ich mich schon gefreut dass ich als Späteinsteiger mithalten kann und dann sowas :nixweiss:

Das mit der exe-Größe liegt, wie schon richtig gesagt, am .net Framework. Momentan baut das Programm auf .net 4 auf.

Okay, Gegenfrage: was habt ihr denn so für (langsame) Rechner?


Mathematiker - Sa 07.07.12 06:32

user profile iconjfheins hat folgendes geschrieben Zum zitierten Posting springen:
Da hatte ich mich schon gefreut dass ich als Späteinsteiger mithalten kann und dann sowas :nixweiss:

Du kannst auf jeden Fall mithalten. Auf dem Rechner mit 1,7 GHz braucht Dein Programm, wie schon gesagt, 99 Sekunden; auf einem zweiten Rechner 2,6 GHz nur 43 Sekunden, ist also richtig schnell.
Für mein Programm dagegen ermittle ich: 82 s auf dem ersten und 56 s auf dem zweiten PC. Du überholst mich also deutlich.
Es liegt scheinbar nicht nur an der Taktfrequenz des Prozessors. Kann es auch an Windows selbst liegen? Der 1.PC läuft mit Vista, der zweite mit Windows 7. Ich habe keine Ahnung.
Beste Grüße
Mathematiker


Horst_H - Sa 07.07.12 08:01

Hallo,

für AMD CPU'S ist die Net Framework wohl nicht so performant.Wie oben geschrieben braucht es mit 3,2 Ghz 54 Sekunden.
Ich hatte gehofft, es wäre eine Core i5/i7 CPU die ja doch erheblich mehr pro Takt erledigen.
Ich verstehe das nicht:
http://www.tomshardware.de/phenom-II-940-phenom-II-920-overclocking-corespannung,testberichte-240232-11.html
Wo taucht denn da die Q6600 auf beim Drystone-Test auf , garnicht mehr!
Bei multimedia-integer (MMX SSE ) aber an allen AMD vorbei, und zwar mehr als doppelt so schnell wie diese.
NET 4.5 habe ich mal probiert. Es bringt eine Reduktion des Speicherbedarfs zur Laufzeit von 18 auf 15 MB und eine Laufzeitreduktion ( bei mir ) von 54 auf 50 Sekunden.
Ich tippe also auf den Einsatz von SSE und Konsorten.
Durch das SumCarryFeld braucht es ja auch keinen Übertrag zwischen den Bytes in der Rechnung, es sind nur Wertzuweisungen.
Aber wir sind noch Ewigkeiten von 51 Sekunden für 250000 Stellen weg.Das sind umgerechnet 8.1 Sekunden für 100000 Stellen und das auf einem Pentium4 2.8 Ghz mit HyperThreading, wenn ich das recht erinnere.
//250000 Stellen in 320 Sekunden bei maximal 22 MB belegt.
Selbst mit Multithreading auf 2 CPUs müssten wir die Geschwindigkeit verdreifachen.

Gruß Horst

P.S.:
Ein paar Nebensächlichkeiten:
Führende Nullen werden bei der Stringeingabe nicht gekürzt und Palindrome wie 990099 werden nicht abgefangen.Auch wenn dieses kein Palindrom in >50000 Iterationen mehr wird.
Das ist Feintuning zum Schluß, falls es den gibt.


Bergmann89 - Sa 07.07.12 17:36

Hey Leute,

ich verfolge das Thema jetzt schon ne Weile. Leider hatte ich bis jetzt noch nicht die Zeit selbst was zu implementieren, bzw. hab ich mich mit Martok unterhalten und er hat meine Illusionen zerstört, jemals einen besseren Algorithmus zu finden als er :D Doch gerade eben unter der Dusche ist mir ein Licht aufgegangen. Wie wäre es, wenn man die Berechnungen auf die Grafikkarte auslagert? Da läuft das Ganze dann schön parallel. Zwei Offscreen-Render-Buffer für die Daten. Der eine zum lesen als Textur gebunden, der zweite als Target für den Renderer. Im nächsten Durchlauf wird dann getauscht. Nen simplen Shader, der die Daten aus der Textur ließt und berechnet. Das einzige wofür mir noch nix eingefallen ist, ist das Carry. Man kann nicht quer über den ganzen Buffer schreiben, sondern nur an dem Pixel, an dem man sich gerade befindet. Wenn wir dazu ne Lösung finden, dann sollte das alles andere in den Schatten stellen. Denn dann wird die komplette Addition von den 3200 Stream-Prozessoren meiner Graka erledigt :mrgreen:
Ne Alternative dazu wäre CUDA, oder ein vergleichbares Toolkit.

MfG Bergmann.


jfheins - Sa 07.07.12 19:32

Hi,
ich habe eine neue Version am Start :) Da die Geschwindigkeit ja doch nicht sooo schlecht scheint habe ich mir die mühe gemacht und die Usability verbessert. (Geschwindigkeit sollte ungefähr gleich geblieben sein)
- Berechnung erfolgt jetzt in einem seperaten Thread
- Berechnung lässt sich pausieren und wiederaufnahmen
- Quellcode wurde kommentiert und aufgeräumt

Wenn ihr auf den "Weiter"-Button klickt, werden nochmal x Iterationen berechnet. Also wenn ihr auf Pause klickt und es wurden 24896 von 50000 Iterationen berechnet und ihr klickt auf Weiter, hört das Programm erst bei 74896 auf. Iterationen und Zeit werden aufsummiert, gemessen wird auch nur die Rechenzeit.
Und damit ihr auch was davon habt, habe ich den Sourcecode angehängt :)
Btw.: Für jede Ziffer einen Int32 zu benutzen ist zwar leider etwas Platzverschwendung, aber deutlich schneller als byte. Int64 ist auf meinem System nochmal 3% schneller....

@Bergmann: Ich hatte auch schon die Idee, meinen Quadcore besser auszulasten. Leider ist das Problem nicht besonders gut parallelisierbar :-(
Eine Iteration ließe sich schon in 4 seperate Teile einteilen, nur der Übertrag zwischen den Blöcken muss dann am Schluss sequentiell eingepflegt werden.
Da eine Iteration auf meiner CPU vll. so 250µs dauert dürfte die Kommunikation und Synchronisierung erheblich sein.
Zumindest bei den Zahlen wie wir sie hier gerade rechnen. Wenn die Zahl groß genug wird, lohnt es sich schon.
Hinzu kommt, dass du die Blockgröße ständig anpassen musst, da die Zahl ja alle paar Iterationen größer wird. Blockgrößen ändern heißt auch wieder Daten austauschen.

Ich hab es gerade mal rechnen lassen, und die Dauer einer Iteration steigt natürlich an. Nach 1 Mio Stellen (2,4 Mio Iterationen) dauert eine Iteration bei mir schon 2,7 ms.

screenshot


Mathematiker - Sa 07.07.12 22:11

Hallo jfheins,
user profile iconjfheins hat folgendes geschrieben Zum zitierten Posting springen:
- Berechnung lässt sich pausieren und wiederaufnahmen

Schön, auf diese Idee hätte ich auch kommen können, bin es aber natürlich nicht.
Die Geschwindigkeit ist auf meinem PC tatsächlich etwa gleich.

user profile iconjfheins hat folgendes geschrieben Zum zitierten Posting springen:
Für jede Ziffer einen Int32 zu benutzen ist zwar leider etwas Platzverschwendung, aber deutlich schneller als byte. Int64 ist auf meinem System nochmal 3% schneller....

Irgendwie müssen PCs doch manchmal vollkommen verschieden sein. Wenn ich bei meinem Programm von byte zu integer übergehe, erhöht sich der Zeitbedarf auf das Dreifache, bei int64 noch einmal auf das Doppelte.
Fazit: Ich brauche einen neuen Computer. :think:

@Bergmann89: Klingt richtig interessant und ich hoffe sehr, dass es Dir gelingt. Wenn ich nur wüsste, wovon du redest. 3200 Stream-Prozessoren??? Ich verstehe gar nichts. :nixweiss:

Beste Grüße
Mathematiker


Hidden - Sa 07.07.12 23:20

Hallo ihr,

auch, wenn ich Zahlentheorie per se nicht so interessant finde (es ist auf dem Gebiet einfach viel zu einfach, Probleme abzuwandeln. So könnte ich jederzeit 50 Aussagen im Stil der Goldbach'schen Vermutung aufstellen, "jede Zahl mit * lässt sich als Summe von n Zahlen mit # schreiben", und wenn mir jemand zeigt, dass die Aussage für n falsch ist vermute ich neu für n+1 :P): hier mal mein Ketchup dazu, wirklich weiterhelfen kann ich euch leider nicht.

Erstmal muss man wie bei jedem Problem, das von der Wahl des Stellenwertsystems abhängt, einsehen, dass es leider hässlich ist. (Tut mir leid, liebes Problem :(). Der Sinn hinter der Einführung von Basen ist eigentlich, dass die betrachteten Operationen Basisunabhängig funktionieren. Eine besondere Eigenschaft hat 196 bestimmt nicht, höchstens das Paar (196, 10).

Ohne Carry ist die Sache eigentlich klar, an Stelle i steht nach n Rekursionsaufrufen:
>> 2^{n-1} \cdot (x[i] + x[len - i]).

Jetzt dachte ich eigentlich, man könnte den Effekt des Carry mit einer unendlichen Matrix beschreiben und davon Potenzen bilden. Leider ändert sich mit Carry ständig die Berechnungsvorschrift, und zwar bei jedem Überlauf von x[len], d.h. wenn sich len ändert.
Wenn das wenigstens ein bisschen seltener passieren könnte, wäre dazwischen durchaus noch etwas Optimierung mit theoretischen Hilfsmitteln drin, so lohnt sich das kaum.

Auch sehr ungünstig ist, dass aus einem Palindrom im nächsten Schritt wieder ein nicht-Palindrom werden kann: Das hat meine bisherigen Bemühungen zerstört, die Anzahl der Prüfvorgänge zwischen Rekursionsaufrufen zu reduzieren. Eventuell lässt sich aber doch noch etwas darüber aussagen, bei welchen Zahlen oder Carrys kein Palindrom entstehen kann; womöglich sogar für die nächsten n Aufrufe. Da sehe ich so das meißte Potential, in diesen Fällen könnte man in einer Schleife mehrere Rekursionsschritte in einem machen.

Wenn für Basis 2 schon gezeigt ist, dass jede Startposition irgendwann ein Palindrom liefert, könnte man auch einmal versuchen, die einzelnen Ziffern durch ihre Binärdarstellung zu substituieren. So erhalten wir eine 0-1-Folge, über die man mit diesem Vorwissen vielleicht etwas aussagen kann.

PS: @Martok, der Mensch ist witzig. :) Was er hier [http://www.p196.org/papers5.html] unten schreibt, trifft auf Mathematik doch gerade nicht zu. Wir können Probleme abstrakt formulieren, die weit größer sind als die Anzahl der Zustände unseres Universums, und deren Wahrheitswert ist unabhängig von der konkreten Prüfbarkeit.
Die meißten Mathematiker akzeptieren ja sogar das Auswahlaxiom, mit dem sich prinzipiell nicht nachvollziehbare und niemals konstruierbare Aussagen ableiten lassen. Sogar die Menge der reellen Zahlen ist schon ein solches Objekt: Die berechenbaren Zahlen (durch eine endliche Maschine) sind abzählbar und damit ein verschwindend kleiner Teil. Das heißt, niemand hat oder wird jemals eine "echte" reelle Zahl sehen :mrgreen:.


BenBE - So 08.07.12 00:08

Hab da mal kurz ne inführung Haskell überflogen und dabei ist das hier rausgefallen:


Erste Schritte in Haskell:
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:
numToList x = numToList' [] x
numToList' y x =
    if x < 10
    then x : y
    else (x `mod` 10) : (numToList' y (x `div` 10))
revList x = reverse x
addList x y =
    if null x
    then (
        if null y
        then []
        else y
    )
    else (
        if null y
        then x
        else (head x + head y) : addList (tail x) (tail y)
    )
daaList x =
    if null x
    then []
    else (daaElemDigit (head x)) : (
        if null (tail x)
        then (
            if 0 == daaHeadCarry x
            then []
            else [daaHeadCarry x]
        )
        else (
            daaList (((daaHeadCarry x) + (head (tail x))) : (tail (tail x)))
        )
    )
daaElemDigit x = x `mod` 10
daaElemCarry x = x `div` 10
daaHeadDigit x = if null x then 0 else daaElemDigit (head x)
daaHeadCarry x = if null x then 0 else daaElemCarry (head x)
isPalim x = x == reverse x
getLyrchInt x = getLyrch (numToList x) 100000
getLyrch x y =
    if y == 0
    then x
    else (
        if isPalim x
        then x
        else (getLyrch (daaList (addList x (reverse x))) (y - 1))
    )


Mathematiker - So 08.07.12 00:12

user profile iconHidden hat folgendes geschrieben Zum zitierten Posting springen:
Wenn für Basis 2 schon gezeigt ist, dass jede Startposition irgendwann ein Palindrom liefert, könnte man auch einmal versuchen, die einzelnen Ziffern durch ihre Binärdarstellung zu substituieren. So erhalten wir eine 0-1-Folge, über die man mit diesem Vorwissen vielleicht etwas aussagen kann.

Tut mir leid, aber im Dualsystem ist es sicher, dass nicht(!) jede Zahl schließlich zu einem Palindrom führt. 10110 wird dort niemals palindromisch.
Ansonsten stimme ich Dir schon zu, dass nicht die 196 an sich besonders ist, sondern nur im Dezimalsystem.
Widersprechen muss ich aber auch. Dieses Problem ist nicht wirklich hässlich, es ist wunderschön. :zwinker:
Schließlich beschäftigt es hier eine Menge Mitstreiter. Und das soll ein Problem ja.
Beste Grüße
Mathematiker


Horst_H - So 08.07.12 07:05

Hallo,

aber der "sittliche Nährwert" ist gegen Null.
Es rettet nicht die Welt, es schadet ihr nicht.
OK, da hat jemand mehrere Jahre seinen Rechner und Umgebung mit geheizt.

@Hidden:
Ohne Carry ist im nächsten Schritt ein Palindrom da.
Schliesslich schreibt man die Summe, der um die mittlere Stelle gespiegelten Ziffern, an diese beiden Stelle.
In dem Text über Beispiele, in denen mit Übertrag ein Palindrom erzeugt wird, http://www.p196.org/files/kruppa.txt , ist die Summe der Ziffern entweder Basis+1, hier also 11 oder beide Ziffern 0.
Diese Zahlen haben ein einfaches Bildungsgesetz.
Erzeuge eine Hälfte einer Zahl, packe dort beliebig Ziffern aus [0,2..Basis-1] rein. Packe an die gespiegelte Stelle die Ergänzung zu Basis+1 oder wenn es 0 ist die 0.Wenn die Zahl eine ungerade Zahl an Ziffern braucht, packe eine 0 in die Mitte.
Wie sagt Homer Simpson: "Langweilig".

Vielleicht sollte man es umdrehen.
Ich habe ein Palindrom, wie viele Aufteilungen in eine Summe zweier Zahlen, deren Ziffern gespiegelt sind, sind möglich.
Wenn man rekursiv betrachtet, kann man jede dieser Zahlen wieder aufteilen.Witzig, die Zahlen werden immer kürzer, aber immer mehr, was natürlich Unsinn ist.
Also weitermachen:
"Ein Neger mit Gazelle zagt im Regen nie"

Gruß Horst


Mathematiker - So 08.07.12 23:35

user profile iconHorst_H hat folgendes geschrieben Zum zitierten Posting springen:
Es rettet nicht die Welt, es schadet ihr nicht.
OK, da hat jemand mehrere Jahre seinen Rechner und Umgebung mit geheizt.

Wow! Die Ursache für die sagenumwobene "globale Erwärmung" ist gefunden. Der 196-Algorithmus! :zustimm:
Ich werde also meinen Rechner jetzt 24 h am Tag laufen lassen. Bei uns schüttet es seit Tagen. Vielleicht wird es ja wärmer.
Nebenbei: Mein aktueller Stand 77 s für 100k Stellen, durch Weiterverwendung der Summe. An Martoks Assembler-Lösung oder jfheins' C#-Programm werde ich aber niemals(!) 'rankommen.

Beste Grüße
Mathematiker


Horst_H - Mo 09.07.12 06:38

Hallo,

jetzt muss man das Verfahren verändern, irgendwie mehrere Ziffern gleichzeitig, wie Martok es mal probiert hat mit BSWAP, aber man darf dabei den Übergang nicht verpassen, wo man dies nicht mehr machen kann.Wenn man 4 Ziffern gleichzeitig verarbeitet muss man bei einem Abstand <8 wieder mit einzelnen Ziffern arbeiten.
Bei Multlithreading kann man das Carry doch zu Beginn schon mal grob abschätzen, indem beispielsweise alle Threads ihre jeweiligen letzten X Ziffern addieren und diesen Übertrag dem nächstem Thread bereitstellen läßt und dann alle gleichzeitig loslegen.
Das es dann eine Korrektur geben muss wird mit zunehmenden X immer unwahrscheinlicher, denn es müsste eine Ziffernfolge nur aus Basis-1 entstehen.Bei 8 Ziffern wohl in der Nähe von Basis^-8.

Gruß Horst


papa69 - Mo 09.07.12 10:55

Hallo zusammen,

ihr habt mich ja von Anfang an mit DIESEM Pa(l)indrom-Virus angesteckt .... grrr ;-)
JA: Mathe macht Spass, wenn es so gut verkauft wird !!!

Aber mal zu was anderem: Es geht ja wohl darum, mit der "196" ein Palindrom zu finden ?!
Auf Geschwindigkeit sollte es dabei eigentlich nicht ankommen.

Letzte Nacht kam mir dann fogende Idee:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
[Denkansatz]

Was ihr braucht:

[*] das Programm, um die (Teil-) Summen auszurechnen
[*] ein weiteres Programm, um diese (Teil-) Summen auf ein Palindrom zu prüfen


Von verteiltem Rechnen habt ihr doch schon gehört !?
Also fehlt noch das 3. (bzw. das nach dem 1. kommende) Programm, 
wechles eine gewisse Anzahl von errechneten (Teil-) Summen dem 2. Programm zur Verfügung stellt...
[/Denkansatz]


jfheins - Mo 09.07.12 14:27

Hi,
also auf Geschwindigkeit kommt es natürlich schon an. Es ist ja (zumindest in meinen Augen) so gut wie sicher, dass 196 niemals zu einem Palindrom wird.
Was wir hier machen ist also ein "Wer schafft die Iterationen in der kürzesten Zeit" oder so ähnlich :)

Dein Ansatz zur Parallelisierung ist leider relativ nutzlos, da die Prüfung relativ flott geht.
Ich habe die Prüfung ja erst nachträglich eingebaut, daher lässt sich das gut vergleichen:
Ohne Prüfung: 241390 Iterationen in 31,09 Sekunden.
Mit Prüfung: 241389 Iterationen in 31,21 Sekunden.

Der Grund dafür dürfte in der Prüfung liegen: Logischerweise bricht man diese sofort ab, wenn sicher ist dass die Zahl kein Palindrom ist. Meistens ist schon noch den ersten 3 Ziffern klar, dass es kein Palindrom ist und die Prüfung ist beendet.


Horst_H - Mo 09.07.12 15:43

Hallo,

ich denke darüber nach, SumCarry um eine Variante zu erweitern und mit einem vierstelligem Feld zu arbeiten.
Kost' ja nix.

Beispiel:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
type 
  record 
      ZiffernfolgeOhneCarry : Dword;
      ZiffernfolgeMitCarry  : Dword;
      CarryOhneCarray       : word;// lieber DWword ?
      CarryMitCarray        : word;// Dword 
  end;

Dazu will ich die Feldposition umrechnen
2497-> (((2*16 +4)*16+9)*16+7 = 2 shl 12 + 4 shl 8 + 9 shl 4 + 7

Beispiel:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
2497 -> (((2*16 +4)*16+9)*16+7 ==> Feldnummer 9367
Feld[9367].ZiffernfolgeOhneCarry = $00040309 ( 0* 4096 +4*256+3*16+9 )
Feld[9367].ZiffernfolgeMitCarry =  $00040400
Feld[9367].CarryOhneCarray = 1
Feld[9367].CarryMitCarray = 1

4455 -> (((4*16 +4)*16+5)*16+5 ==> Feldnummer 17493
Feld[17493].ZiffernfolgeOhneCarry = $09090909
Feld[17493].ZiffernfolgeMitCarry =  $00000000
Feld[17493].CarryOhneCarray = 0
Feld[17493].CarryMitCarray = 1


Fragt sich nur, ob die Rechnerei für die Feldposition nicht zu aufwendig ist.
Oder man sogar mit *Basis statt shl rechnen kann, das spart ja einiges an Platz. ( Nur noch 120 kb )

In der untere Hälfte Der neuen Zahl kann man Carry ja mitnehmen, aber in der oberen nicht.
Deshalb habe ich an ein Feld/Liste für die Übertrage der oberen Hälfte gedacht, indem nur Pos in der Zahl, die den Übertrag korrigiert haben muss, eintrage.
Mal schauen, ob ich richtig ziele.

Gruß Horst


Palladin007 - Di 10.07.12 02:30

Morgen ^^


Das Eingangs-Problem ist ein mathematisches Problem und damit ein gutes Problem zum Freizeit-Vertreib mit Programmieren. ^^

Ich hab mich auch mal daran versucht und erst ein Konsolen-Programm geschrieben und anschließend eine Forms-Anwendung, die die gleichen Methoden nutzt. exe und Quellcode im Anhang.

Da ich das bisher mit Decimalzahlen gemacht habe, liege ich nach dem, was mir Mathematiker gesagt habe, etwas hinten, oder?
Es wäre trotzdem nett, wenn ihr mal da drüber schauen und euren Senf abgeben könnt. ^^
Wer dabei nur an der Berechnung interessiert ist, schaut einfach die statische Klasse Palindrom an, da ist alles drin.
Kommentiert habe ich noch nicht, kann das aber gerne auf Nachfrage machen.


Da ich selber nur wenig Ahnung davon habe, wie man im Dualsystem rechnet, würde ich mich freuen, wenn mir jemand eine kurzen Crash-Kurs gibt, wie das abläuft und zu welchen Schlüssen ihr hier bisher gekommen seid, da ich von Delphi keine Ahnung habe und mir das drei Seiten lange lesen ersparen würde.
Wenn nicht, dann muss ich mich wohl hinein lesen -.-
Auf jeden Fall werde ich mir dazu das letzte Programm von jfheins anschauen und mir etwas abkupfern. :P


Aber bis dahin ist das meine Kampfansage für den Wettbewerb, bei dem ich nun freudig mit mischen werde. ^^


Gruß


Horst_H - Di 10.07.12 06:59

Hallo,

guten Morgen, ich erhalte eine unerwartete Ausgabe:


Quelltext
1:
2:
3:
4:
5:
Eingabe:  196
Es gab einen Fehler:
    Der Wert liegt außerhalb des erwarteten Bereichs.

Gesammte Zeit: 0 ms


Gruß Horst
EDIT:
Wo war ich denn mit meinen Gedanken, einen Post darüber.
Zeile = Zahl1 und Spalte = Zahl2 geht ja vom Platz her fast nicht, und Zugriffszeit auf den Hauptspeicher ist ja dann ewig.
Es sind nur 10000+9999 Felder und diese sehen so aus. (
Zahl1, Zahl2 umrechnen nach word Summe bilden und dann auf das Feld zugreifen:


Delphi-Quelltext
1:
2:
3:
4:
5:
Summe "2442" -> (((2*10 +4)*10+4)*10+2 ==> Feldnummer 2442
Feld[9367].ZiffernfolgeOhneCarry = $02040402 
Feld[9367].ZiffernfolgeMitCarry =  $02040403
Feld[9367].CarryOhneCarray = 0
Feld[9367].CarryMitCarray = 0


Diese Umrechnerei ist aber langsam, da gibt sicher nach ein analoges "ASCII" -> INT, das schnell ist.


Palladin007 - Di 10.07.12 18:33

@Horst_H:

Das liegt daran, dass die Zahl bei der Berechnung den Wertebereich sprengt.
Das kann ich auch nicht ändern, bis ich nicht eine Möglichkeit gefunden habe, größere Zahlen zu berechnen.
Die Gesamtzeit darunter berechnen die Zeit aller Berechnungen.
Du kannst beliebig viele Zahlen eingeben, aber der Fehler oben wird immer auftreten, wenn eine Zahl im Berechnungs-Prozess den Wertebereich von double sprengt.
196 ist dabei die einzige kleine Zahl, die mir aufgefallen ist, dass sie diesen Fehler bringt. Alle anderen haben dann schon eine große Zahl Stellen und auch dann heißt das nicht, dass sie den Bereich sprengen.


PS: Da fällt mir ein, dass ich an einer Stelle Blödsinn gemacht habe...
Ich glaube, irgendwo habe ich UInt64 verwendet, was ja deutlich geringer ist, als double.
Bei meinem Konsolen-Programm war das durchaus sinnvoll, allerdings jetzt, wo die Eingabe-Kontrolle durch einfache-char-Überprüfung gesichert ist, kann ich dort double nutzen.


Horst_H - Di 10.07.12 19:16

Hallo,

double ist wohl etwas klein geraten.Bisher rechnen die meisten hier bis 1E5 Stellen.

Ich habe mal in der Additionschleife zählen lassen, wie oft ein Carry vorkommt und wie oft eine Stelle addiert wird.
Wenn ich das richtig gerechnet habe, sind es 2,4*(100000*100001/2)=~12x10E9 Additionen für 100000 Stellen, was auch passt.
Das Verhältnis CarryCnt/AddsCnt ist fast genau 0.5 .

Quelltext
1:
2:
3:
4:
5:
...952584506637311513
    241389 it    100000 dig

Einzelbyte Additionen  12080757065
Einzelbyte Uebertraege  6040358167

Bei 30 Sekunden und 3 GHz? sind das 120 Takte pro Byte Addition+Reverse.
Das ist doch erschreckend langsam.Meine 54 Sekunden noch viel mehr.


Gruß Horst


Palladin007 - Di 10.07.12 19:37

Ich habe jetzt alles so geändert, dass nur noch double genutzt wird.
Der Wertebereich liegt dann noch über 1E307. Die genaue Zahl weiß ich gerade nicht aus dem Kopf.

Allerdings hab ich jetzt ein paar komische Fehler, die ich erst finden und beheben muss, bevor ich das neue Programm zeigen kann.


Horst_H - Mi 11.07.12 21:39

deleted


Horst_H - Fr 13.07.12 08:50

Hallo,


Das mit dem Carry ist nicht so schön, wie erhofft hatte.

Für user profile iconMartok ist sicher die C-Variante von Eric Goldstein mit Benutzung von SSE interessant.Da kann man sicher die Aufrufkonvention cdecl benutzen.
Das wäre sicher eine extrem schnelle Variante.

Gruß Horst


Horst_H - So 15.07.12 11:20

Hallo,

eine etwas schnellere Version ( als p196.dpr speichern )
15.1s für 100000 Stellen mit freepascal 2.6.4 ( -Xs -XX ).
Addition und Carry-Berechnung und getrennt.Dies war entscheidend.
Dann enthalten die Bytes statt 0..9 nun 0..18, diese Zahlen werde erst anschliessend immer zwei benachtbarte auf einmal korrigiert.
Carry Bestimmung braucht den Großteil der Rechenzeit.
Den Assemblerkram hätte man sich fast sparen können ;-)
Speicherzugriff ist teuer.Deshalb eine Variante, in der der Carry und neue Zahl in einem Word gespeichert und dann per Ausmaskierung und shift right umgemodelt werden


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:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
248:
249:
250:
251:
252:
253:
254:
255:
256:
257:
258:
259:
260:
261:
262:
263:
264:
265:
266:
267:
268:
269:
270:
271:
272:
273:
274:
275:
276:
277:
278:
279:
280:
281:
282:
283:
284:
285:
286:
287:
288:
289:
290:
291:
292:
293:
294:
295:
296:
297:
298:
299:
300:
301:
302:
303:
304:
305:
306:
307:
308:
309:
310:
311:
312:
313:
314:
315:
316:
317:
318:
319:
320:
321:
322:
323:
324:
325:
326:
327:
328:
329:
330:
331:
332:
333:
334:
335:
336:
337:
338:
339:
340:
341:
342:
343:
344:
345:
346:
347:
348:
349:
350:
351:
352:
353:
354:
355:
356:
357:
358:
359:
360:
361:
362:
363:
364:
365:
366:
367:
368:
369:
370:
371:
372:
373:
374:
375:
program p196;
{$IFDEF FPC}
  {$MODE DELPHI}
  {$ASMMODE INTEL}
  {$Optimization ON}
  {$Optimization RegVar}
  {$Optimization PEEPHOLE}
  {$Optimization CSE}
  {$Optimization ASMCSE}
{$Else}
  {$APPTYPE console}
{$Endif}

uses
  SysUtils;

{$IFNDEF FPC}
type
  nativeUint = LongWord;//Uint64
  PtrUint  = LongWord;
{$Endif}

const
  NUMBER_BASE = 10;
  cMaxDigit = 100000//quadratische Laufzeit n,t->k*n,k^2*t
  cDelta = 24139;//Ausgabe alle cDelta Berechnungen

  CpuF = 3.5e9;  // CPU-Frequenz

type
  TDigit  = byte;
  tDigit2 = word;
  tpDigit =^TDigit;
  tpDigit2 =^TDigit2;
  // Rueckwaerts, niederwertigste Stelle ist [0]
  TNumber = array of TDigit;
  tSumCarry = record
                we: Word; 
//              ca:word; carry und neuer Wert separat
              end;
//ein nicht gelungener Versuch, Platz zu sparen 
//aber die Indexbestimmung wird zu langsam
//tSumCarFeld = array[0..18*32+21] of tSumCarry;
  tSumCarFeld = array[0..18*256+21of tSumCarry;
  
var
  MaxIndex : integer;
  Cycle: Cardinal;
  AddCnt : Int64;
  cSumCarFeld : tSumCarFeld;

procedure NumberFromString(var Number: TNumber; Str: string);
var
  i: integer;
begin
  SetLength(Number, Length(Str));
  for i:= 0 to Length(Str) - 1 do
    Number[i]:= Ord(Str[Length(Str) - i]) - Ord('0');
end;

function NumberToString(var Number: TNumber): string;
var
  i,l: integer;
begin
  i := High(Number);
  If i <= 50 then
  begin
    l := i+1;
    SetLength(Result, l);
    for i:= 0 to High(Number) do
    begin
      Result[l]:= Chr(Ord('0') + Number[i]);
      dec(l);
    end;
  end
  else
  begin
    SetLength(Result, 50);
    fillchar(Result[1],50,'.');
    For l := 1 to 24 do
    Begin
      Result[l] := Chr(Ord('0') + Number[i-l+1]);
      Result[l+26] := Chr(Ord('0') + Number[24-l]);
    end;
  end;
end;

function NumAddAs(pDigit1,pDigit2 : tpDigit):NativeUInt;
//Mal sehen, ob moeglichst viele Register helfen
//Aber es dauert so nur etwa 0.25..0.32 Takte/Digit
begin
  asm
//   PUSH     ECX; PUSH     ESI;PUSH   EDI;
// Bei dermaßen vielen Stellen geht das unter.
     PUSHAD

     MOV     ESI,pDigit2
     MOV     EDI,pDigit1

     MOV     ECX,ESI
     SUB     ECX,EDI
     INC     ECX
     SAR     ECX,3
     TEST    ECX,ECX
     MOV     @result,ECX;
     JLE     @UndTschuesz
//EDI von Byte auf LongWord zeigen lassen
     SUB     ESI,3

     CMP     ECX,4
     JLE     @Reste
//     JMP     @Reste
@WeiteSchritte:
     SUB     ECX,4

     MOV     EAX,[ESI]
     BSWAP   EAX
     MOV     EBX,[ESI-4]
     BSWAP   EBX
     ADD     EAX,[EDI]
     MOV     EBP,[ESI-8]
     BSWAP   EBP
     ADD     EBX,[EDI+4]
     MOV     EDX,[ESI-12]
     BSWAP   EDX
     ADD     EBP,[EDI+8]
     ADD     EDX,[EDI+12]

     MOV     [EDI],EAX
     BSWAP   EAX
     MOV     [EDI+4],EBX
     BSWAP   EBX
     MOV     [EDI+8],EBP
     BSWAP   EBP
     MOV     [EDI+12],EDX
     BSWAP   EDX

     ADD     EDI,16

     MOV     [ESI],EAX
     MOV     [ESI-4],EBX
     MOV     [ESI-8],EBP
     MOV     [ESI-12],EDX

     SUB     ESI,16
     CMP     ECX,4

     JG      @WeiteSchritte;

@Reste:
     MOV     EAX,[EDI]
     BSWAP   EAX
     MOV     EDX,[ESI]
     ADD     EAX,EDX
     MOV     EDX,EAX
     BSWAP   EDX
     MOV     [ESI],EAX
     SUB      ESI,4
     MOV     [EDI],EDX
     ADD      EDI,4
     DEC     ECX
     JNE     @Reste;

@UndTschuesz:
     POPAD
//     POP     EDI; POP     ESI;POP     ECX
  end;
end;

function CarryCorrect(pDigit: tpDigit;cnt: integer):nativeUint;
//Das braucht ~13 Takte pro Digit :-(
//freepascal 2.6.2 div wird durch mul und shr ersetzt
var
 Carry: nativeUint;
begin
  Carry := 0;
  for  cnt := cnt Downto 0 do
  begin
    Carry := Carry+pDigit^;
    result := Carry DIV 10;
    pDigit^ := Carry-10*result;
    inc(pDigit);
    Carry := result;
  end;
end;

function CarryCorrect2(pDigit: tpDigit2;cnt: integer):nativeUint;
//Zwei Zahlen auf einmal korrigieren
var
 idx: NativeUint;
begin
  result := 0;
  //0..cnt hat cnt+1 Elemente
  dec(cnt);
  IF cnt > 0 then
    repeat
      with cSumCarFeld[pDigit^+result] do
      begin
        idx := we;
        pDigit^ := idx shr 2;
        result  := idx AND 3;
      end;
      inc(pDigit);
      dec(cnt,2);
    until cnt<0;

  IF cnt >-2 then
  begin
    // Da fehlt ein Byte
    // Zugriff via word ist nicht sauber.
    // Aber oberes Byte bleibt unangetastet
    idx := (pDigit^ AND 255 )+result;
    result := idx DIV Number_Base;
    idx := idx -result*Number_Base;// ( idx MOD Number_Base)
    pDigit^ := pDigit^ AND (255*256)+idx;
  end;
end;

procedure NumberAdd(var Number : TNumber);
// erst alles addieren und anschliessend carry
// uebertragen bringt einiges
var
  pDigit1,pDigit2 : tpDigit;
  p,
  Carry: nativeUint;
begin
  p := high(Number);
  inc(AddCnt,  (p+1DIV 2);
  pDigit1 := @Number[0];
  pDigit2 := pDigit1;
  inc(pDigit2,p);// @Number[p];
//{ Mit Assembler
  //Wieviele Stellen mit Assemblerroutine bearbeitet
  Carry:=SizeOf(PtrUint)*NumAddAs(pDigit1,pDigit2);
  //Die Zeiger entsprechend anpassen
  inc(pDigit1,Carry);
  dec(pDigit2,Carry);
//}

  //die letzten einzelnen Stellen addieren
  Carry:= 0;
  IF PtrUint(pDigit2)>= PtrUint(pDigit1) then
    repeat
    Carry := pDigit1^+pDigit2^;
    pDigit1^ := Carry;
    pDigit2^ := Carry;
    inc(pDigit1);
    dec(pDigit2);
    until PtrUint(pDigit2) < PtrUint(pDigit1);

  IF CarryCorrect2(@Number[0],p) = 1 then
    begin
    p := Length(Number)+1;
    SetLength(Number, p);
    Number[p-1] :=1;
    end;
end;

function NumberCompare(const A: TNumber): boolean;
var
  i,j: integer;
begin
  i := Low(A);
  j := High(A);
  Repeat
    Result:= A[i]=A[j];
    inc(i);
    dec(j);
  until Not(Result) OR (J<I);

  IF  MaxIndex <i then
  begin
    MaxIndex := i;
    writeln('Erster Unterschied bei Stelle ',i:2,' und Iteration ',cycle)
  end;
end;

procedure SumCarFeldErstellen;
// Für zwei Stellen auf einmal
var
  i,j,s,c,d1,d2,idx: integer;
begin
  Fillchar(cSumCarFeld,SizeOf(cSumCarFeld),#0);
  For i := 0 to 18 do
    For j := 0 to 21 do
    begin
      idx := i*256+j;
      s := (i*Number_Base+j);
      c := s DIV sqr(Number_Base);
      s := s - c* sqr(Number_Base);
      d1 := s DIV Number_Base;
      d2 := s-d1*Number_Base;
      with cSumCarFeld[idx] do
      begin
        we := (d1*256+d2)*4+c;
      end
    end;
end;

var
  Work : TNumber;
  delta: cardinal;
  Time1, Time2 : TDateTime;
  s: string;
begin
  SumCarFeldErstellen;

  Repeat
    Write('Startzahl: ( Beenden mit 0 ) ');
    ReadLn(s);
    if s='0' then
      break;
    If s= '' then
      s := '196';
    writeln(s);
    NumberFromString(Work, s);
    Cycle:= 0// das erste ist kein cycle...
    AddCnt := 0;
    delta := cDelta;
    Time1 := time;
    Repeat
      inc(Cycle);
      NumberAdd(Work);
      //Keine Ausgabe !
      if Length(Work) >= cMaxDigit then
        break;
      dec(delta);
      if Delta=0  then
        begin
        delta := cDelta;
        Time2 := time;
        WriteLn(Cycle:10,' it',Length(Work):10,' dig',(Time2-Time1)*86400.0:10:3,' s');
        //WriteLn(NumberToString(Work));readln;
        end;
    until NumberCompare(Work);
    Time2 := time;
    WriteLn(NumberToString(Work));
    WriteLn(Cycle:10,' Iterationen mit ',AddCnt,' Byte-Additionen ' );
    time1 := (Time2-Time1)*86400;
    IF time1 > 0 then
    begin
      WriteLn('Takte/ Addition ',(Time1*CpuF)/AddCnt:6:3);
      WriteLn(Length(Work):10,' Digits ',Time1:10:7,' s');
    end;
  until true;
end.
{# ./p196_6
Startzahl: ( Beenden mit 0 )
196
Erster Unterschied bei Stelle  1 und Iteration 1
Erster Unterschied bei Stelle  2 und Iteration 6
Erster Unterschied bei Stelle  3 und Iteration 13
Erster Unterschied bei Stelle  4 und Iteration 16
Erster Unterschied bei Stelle  5 und Iteration 25
Erster Unterschied bei Stelle  8 und Iteration 70
Erster Unterschied bei Stelle 12 und Iteration 105
Erster Unterschied bei Stelle 13 und Iteration 1237
Erster Unterschied bei Stelle 14 und Iteration 1430
Erster Unterschied bei Stelle 16 und Iteration 3461
Erster Unterschied bei Stelle 17 und Iteration 3940
Erster Unterschied bei Stelle 29 und Iteration 9777
     24139 it     10019 dig     0.152 s
     48278 it     20054 dig     0.605 s
     72417 it     30072 dig     1.360 s
     96556 it     40075 dig     2.418 s
    120695 it     50116 dig     3.780 s
    144834 it     60047 dig     5.443 s
    168973 it     70007 dig     7.407 s
    193112 it     80054 dig     9.675 s
    217251 it     90053 dig    12.239 s
131611373561547534929710..990802952584506637311513
    241389 Iterationen mit 6040318143 Byte-Additionen
Takte/ Addition  8.752
    100000 Digits 15.1040000 s
}


Gruß Horst


Horst_H - Mo 18.08.14 14:20

Hallo,

Mist, die sind schon viel weiter, im April dachte ich mal an BCD Arithmetik,..ich bin alt...
http://www.dolbeau.name/dolbeau/p196/p196_mpi.pdf
http://www.isc-events.com/isc14/tl_files/isc/posters/14_Poster_Swierczewski.pdf

Eigentlich wollte ich noch mal testen, wie viele Stellen man als Schutzstellen braucht.
Ich nehme zum Beispiel nur die ersten 1000 Stellen von vorn/hinten und bestimme damit die Anzahl der Berechnungen bis zum Überlauf.Damit wollte ich dann die mittleren threads rechnen lassen.
Es war doch im Mittel alle 2,41... ein Überlauf, wenn ich mit 2* 7500 Stellen pro Thread Abschnitt rechnen will und 50 zusätzliche Stellen berechnen möchte( ~ 50*2,4~ 120 Durchläufe ), muss ich die Abfolge der Überläufe kennen, weil sich ja dann die oberste Stelle um 1 verschiebt und die Anzahl an Schutzstellen, das da kein Spökes/Unsinn rauskommt.
Wenn A/B der Kernberech den ich berechnen will ist, möchte ich V/W wissen, den ich mitschleppen muss:
....VVAAAAAAVV.......Mitte.......WWBBBBBBWW....-> VVAAAAAAVVWWBBBBBBWW also dicht beisammen.
Aber das habe ich noch nicht getestet.
Die Schreiben in ihrer Abhandlung, dass die Speichergeschwindigkeit beim Einsatz von SSE Berechnung bremst ( Die Addition wird schon "normal" gebremst ) deshalb mein Gedanke, es möglichst im Level I Cache zu belassen und möglichst wenig zu synchronisieren.
Momentan kommen sie nicht über 1,4E10 Stellen/Sekunde ab 6 Threads und SSE4 :D
Die hätten das mal genauer benennen sollen. 1 Byte Additionen + Carry Berechnungen ( Für die ersten 1 Mio Stellen brauchte es bei mir 604,2x1E9 Berechnungen und es dauerte 1000 Sekunden = 23 mal langsamer :-( ( Selbst bei optimalen 6 Threads ein Faktor 4 ).

Gruß Horst
EDIT:
Die angehängte Datei war die falsche Version...
Edit2:
Darum ging es, assembler Varinaten die direkt 4-er Gruppen verarbeiten.
Zum Schluss werden die restlichen Byte per Pascal berechnet


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:
function NumAddAs(pDigit1,pDigit2 : tpDigit):NativeUInt;
//Mal sehen, ob moeglichst viele Register helfen
//Aber es dauert so auch etwa 0.25..0.32 Takte/Digit oder war das bei 64Bit??
//4er-Gruppe Laden und umsortieren mit Bwap-> 
//mit Gegestelle addieden->dort speichern und 
//umsortieren und wieder am Original speichern

//[p1]ABCD -> DCBA -> DCBA+ [p2]VWXY = DV CW BX AY -> Speichern[p2] und  DV CW BX AY -> AY BX CW DV  speichern[p1]
begin
  asm
//   PUSH     ECX; PUSH     ESI;PUSH   EDI;
// Bei dermaszen vielen Stellen geht das unter.
     PUSHAD

     MOV     ESI,pDigit2
     MOV     EDI,pDigit1

     MOV     ECX,ESI
     SUB     ECX,EDI
     INC     ECX
     SAR     ECX,3
     TEST    ECX,ECX
     MOV     @result,ECX;
     JLE     @UndTschuesz
//EDI von Byte auf LongWord zeigen lassen
     SUB     ESI,3

     CMP     ECX,4
     JLE     @Reste

@WeiteSchritte:
     SUB     ECX,4

     MOV     EAX,[ESI]
     BSWAP   EAX
     MOV     EBX,[ESI-4]
     BSWAP   EBX
     ADD     EAX,[EDI]
     MOV     EBP,[ESI-8]
     BSWAP   EBP
     ADD     EBX,[EDI+4]
     MOV     EDX,[ESI-12]
     BSWAP   EDX
     ADD     EBP,[EDI+8]
     ADD     EDX,[EDI+12]

     MOV     [EDI],EAX
     BSWAP   EAX
     MOV     [EDI+4],EBX
     BSWAP   EBX
     MOV     [EDI+8],EBP
     BSWAP   EBP
     MOV     [EDI+12],EDX
     BSWAP   EDX

     ADD     EDI,16

     MOV     [ESI],EAX
     MOV     [ESI-4],EBX
     MOV     [ESI-8],EBP
     MOV     [ESI-12],EDX

     SUB     ESI,16
     CMP     ECX,4

     JG      @WeiteSchritte;

@Reste:
     MOV     EAX,[EDI]
     BSWAP   EAX
     MOV     EDX,[ESI]
     ADD     EAX,EDX
     MOV     EDX,EAX
     BSWAP   EDX
     MOV     [ESI],EAX
     SUB      ESI,4
     MOV     [EDI],EDX
     ADD      EDI,4
     DEC     ECX
     JNE     @Reste;

@UndTschuesz:
     POPAD
//     POP     EDI; POP     ESI;POP     ECX
  end;
end

function CalcCarryAs(var a:tDigit2;cnt:NativeInt):NativeInt;assembler;
//Soll die Ueberträge/Carry von 8 Digits bestimenn {Minimum 8 Byte}
//Anaolg zu BCD-Add
//http://www.azillionmonkeys.com/qed/asmexample.html
//Norbert Juffa has given me some devilishly
//clever code for performing a 64 bit BCD addition
//statt lea   edi, [eax+66666666h]
//eben Byte-weise
//MOV  EDX,EAX
//ADD  EDX,246*($01010101)// $F6F6F6F6 // OK LEA EDX,[EAX+$F6F6F6F6] ginge auch
  asm
  PUSH EDI
  PUSH ESI
  PUSH EBX
  PUSH ECX
  PUSH EBP

  MOV  EDI,a
  XOR  EAX,EAX   // Carry = 0;
  MOV  ESI,cnt
  SUB  ESI,8
// Uberhaupt etwas zu tun? Minimum 8 Byte
  JL   @WarWohlNichts

@Loop:
  ADD  EAX,[EDI] // Carry einarbeiten
  MOV  EBP ,[EDI+4]
  SUB  ESI,8
  MOV  EDX,EAX
  ADD  EDX,246*($01010101)
  SBB  EBX,EBX  // Carry merken
  XOR  EAX,EDX
  AND  EAX,$01010101
  ADD  EBX,EBX
  ADC  EBP,0    // auf folgende Zahl uebetragen
  SHL  EBX,1
  MOV  ECX,EBP
  RCR  EAX,8    // Carry reinrollen
  ADD  ECX,246*($01010101)

  XOR  EAX,($01010101)
  IMUL EAX,EAX,246
  SUB  EDX,EAX
  MOV  [EDI], EDX

  SBB  EAX,EAX  // Carry merken
  XOR  EBP,ECX
  AND  EBP,($01010100)
  ADD  EAX,EAX
  RCR  EBP,8 // Carry reinrollen
  XOR  EBP,($01010101)
  IMUL EBP,EBP,246
  SUB  ECX,EBP
  MOV  [EDI+4], ECX

  SHR  EAX,31
  ADD  EDI,8
  TEST ESI,ESI
  JGE  @Loop

@WarWohlNichts:
  POP  EBP
  POP  ECX
  POP  EBX
  POP  ESI
  POP  EDI
end;