64/32 Bit Division in AWL

Zuviel Werbung?
-> Hier kostenlos registrieren
Wo ist der Unterschied?

(251 * 256 / 252) + 1 = 254 +1 = 255 (Soll 255,9 )

Die Ungenauigkeit von 0.98 bekommst Du halt nicht weg. Da beißt die Maus keinen Faden ab. Es sei denn, Du prüfst das erste Ergebnis(251 * 256 / 252) auch noch mal auf den Rest ab. Dann kannst Du bei Bedarf auch noch ab oder aufrunden. Das Prinzip bleibt doch aber immer gleich. Natürlich musst Du aber Deine Dividierhäppchen so klein wählen, das sie nicht größer sind als die Hälfte Deines Rechenregisters. In Deinem Falle wären die Hälfte also 16 Bit.

Es muss ja auf jeden Fall noch die Multiplikation von 2-8Bit Werten reinpassen.



Danke, das habe ich bisher nicht bedacht.
Dann reicht es also nicht, wenn ich meine 64 Bit in 2x32Bit aufteile, sondern ich muss die 32 Bit nochmal teilen?
 
Sorry,
Ich hatte es so verstanden, dass b noch unter dem Bruchstrich steht.

Poste mal dein Programm.

Wenn du die beiden Subtraktionen in Wörtern ablegst (kein Problem) und diese dann multiplizierst, dann kommt doch eine größere Zahl raus als die SPS damit umgehen kann.

also 5.000.000 * 5.000.000 = geht nicht!
Die SPS kann diese Zahlen nicht multiplizieren.
Ich raffs nicht!!!

Gruß wolder
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hier ein Zitat von Perfektionist:
Ich glaube er hat das mit der Multiplikation verständlicher erklärt.


Also, wenn ich recht verstanden habe, zweifelte Grubba ja an, dass Du einen Divident mit 64 Bit vorliegen hast. Ich gehe mal davon aus, dass Du die Multiplikation derart durchgeführt hast, dass Du die zwei 32-Bit-Zahlen zunächst von ihren Vorzeichen befreit hast, dann in jeweils zwei 16-Bit-Zahlen zerlegt hast und dann entsprechend vier mal multipliziert.
Code:
z=x*y
x=65536a+b
y=65536c+d
z=(65536a+b)(65536c+d)
 =65536*65536ac+65536bc+65536ad+bd
habe ich das mal soweit richtig erfasst?

Bei Deiner Beschreibung der Division vermisse ich, wie Du mit dem Divisionsrest der ersten Division verfahren bist ...
 
Zuletzt bearbeitet:
ja, ich denk auch schon die ganze Zeit nach, ob sich mit den 32-Bit-Registern der S7 elegant eine Division machen lässt, bei der der Divisor den Wertebereich einer Integer-Zahl übersteigt. Wäre der Divisor ein Integer (16Bit), so wäre es einfach, den Divident in vier Divisionsschritten "kleinzumachen".

Was bleibt, ist, das ganze mit der Hand am Arm Bit für Bit durchzupopeln. um hier nicht unnötig lange das Prinzip zu erklären, möchte ich einfach ein auf 8/4-Bit verkürztes Beispiel darstellen (im Prinzip geht es so, wie in der Schule mit dem Dezimalsystem gelernt):
Code:
dividiere 45 durch 6 (das ist sieben Rest drei)
00101101:0110=00000111
00101101mod0110=0011
 
0000 0010 1101     // Den Speicher für den Divident um die Länge des Divisors nach links erweitern
 
0000 0101 1010    // und eins nach links schieben
0110 --> geht 0-mal rein
 
0000 1011 0100
0110 --> 0-mal
 
0001 0110 1000
0110 --> 0-mal
 
0010 1101 0000
0110 --> 0-mal
 
0101 1010 0000
0110 --> 0-mal
 
1011 0100 0000
0110 --> 1-mal
 
0101 0100 0000  // ist dann der Rest, der weiter zu dividieren ist
 
1010 1000 0000  // und wieder weiter geschoben
0110 --> geht einmal rein (also wieder 1 notieren)
 
0100 1000 0000  // wieder der Rest davon
 
1001 0000 0000
0110 --> 1-mal
 
0011 0000 0000  // nach achtmal Schieben steht hier nun Rest drei
 
 
die mitgeschriebenen 0-mal bzw. 1-mal sind dann wie gewünscht: 0000 0111
Das durchzuführen ist in AWL kein prinzipielles Problem. Mit dem Loopbefehl und den Rotierbefehlen , dem 32-Bit-Vergleich und 32-Bit-Subtraktion ist es möglich, den Code einigermassen überschaubar zu halten. Allerdings wird die Division auf diese Art und Weise merklich mehr Ausführungszeit als die 32-Bit-Multiplikation mit 64-Bit-Ergebnis benötigen (wundert es jemanden?). Aber das Ergebnis wird genau.

Ich persönlich habe allerdings in einem vergleichbaren Fall mich damit begnügt, nur die ersten 32 signifikanten Bits der 64-Bit-Zahl durch die ersten signifikanten 16 Bit der 32-Bit-Zahl zu dividieren. klar - das Ergebnis ist entsprechend ungenauer :rolleyes: und der Aufwand, sich die Zahlen zurechtzuschieben auch nicht zu verachten (irgendwie konnte ich das aufgrund vorgegebener Wertebereiche noch vereinfachen).
 
Zuviel Werbung?
-> Hier kostenlos registrieren
ja, ich denk auch schon die ganze Zeit nach, ob sich mit den 32-Bit-Registern der S7 elegant eine Division machen lässt, bei der der Divisor den Wertebereich einer Integer-Zahl übersteigt. Wäre der Divisor ein Integer (16Bit), so wäre es einfach, den Divident in vier Divisionsschritten "kleinzumachen".



Das durchzuführen ist in AWL kein prinzipielles Problem. Mit dem Loopbefehl und den Rotierbefehlen , dem 32-Bit-Vergleich und 32-Bit-Subtraktion ist es möglich, den Code einigermassen überschaubar zu halten. Allerdings wird die Division auf diese Art und Weise merklich mehr Ausführungszeit als die 32-Bit-Multiplikation mit 64-Bit-Ergebnis benötigen (wundert es jemanden?). Aber das Ergebnis wird genau.

Ich persönlich habe allerdings in einem vergleichbaren Fall mich damit begnügt, nur die ersten 32 signifikanten Bits der 64-Bit-Zahl durch die ersten signifikanten 16 Bit der 32-Bit-Zahl zu dividieren. klar - das Ergebnis ist entsprechend ungenauer :rolleyes: und der Aufwand, sich die Zahlen zurechtzuschieben auch nicht zu verachten (irgendwie konnte ich das aufgrund vorgegebener Wertebereiche noch vereinfachen).




Vielen Dank, das ist echt sehr hilfreich.
Ich glaube allerdings nicht, dass ich mich auf die signifikanten Bit beschränken darf.
Aber ich werde das auf jeden Fall mal so ausprobieren und mich dann nochmal melden.
 
nun hab ich mal meine Mittagspause damit verbracht, so eine Division zu stricken - hier mal Code, aber ungetestet!

Code:
FUNCTION "div64-32" : VOID
TITLE =
VERSION : 0.1
 
VAR_INPUT
  I_Divident_H : DWORD ; 
  I_Divident_L : DWORD ; 
  I_Divisor : DINT ; //negative Zahlen unzulässig
END_VAR
VAR_OUTPUT
  Q_Quotient_H : DWORD ; 
  Q_Quotient_L : DWORD ; 
  Q_Modulo : DWORD ; //oder auch "Rest"
END_VAR
VAR_TEMP
  T_Divident_2 : DINT ; 
  T_Divident_1 : WORD ; 
  T_Divident_0 : WORD ; 
  T_Quotient_1 : WORD ; 
  T_Quotient_0 : WORD ; 
  T_Schleifenzaehler : BYTE ; 
  T_Zwischenergebnis : BOOL ; 
END_VAR
BEGIN
NETWORK
TITLE =
      L     64; // Initialisierung
      T     #T_Schleifenzaehler; 
      L     0; 
      T     #T_Divident_2; 
      L     #I_Divident_H; 
      T     #T_Divident_1; 
      L     #I_Divident_L; 
      T     #T_Divident_0; 
m001: NOP   0; // Schleifeneinsprungpunkt
      L     #T_Divident_0; // Dividentregister eins links schieben
      RLDA  ; 
      T     #T_Divident_0; 
      L     #T_Divident_1; 
      RLDA  ; 
      T     #T_Divident_1; 
      L     #T_Divident_2; 
      RLDA  ; 
      T     #T_Divident_2; 
      L     #I_Divisor; 
      <D    ; 
      R     #T_Zwischenergebnis; 
      SPB   m002; 
      =     #T_Zwischenergebnis; // wird hier auf 1 gesetzt
      -D    ; // und der Divident ...
      T     #T_Divident_2; // ... entsprechend vermindert
m002: L     0; // Zwischenergebnis in das Ergebnisregister ...
      UN    #T_Zwischenergebnis; 
      SPB   m003; 
      L     DW#16#80000000; 
m003: RLDA  ; // ... von rechts nach links reinschieben
      L     #T_Quotient_0; 
      RLDA  ; 
      T     #T_Quotient_0; 
      L     #T_Quotient_1; 
      RLDA  ; 
      T     #T_Quotient_1; 
      L     #T_Schleifenzaehler; // wiederholen
      L     1; 
      -I    ; 
      T     #T_Schleifenzaehler; 
      SPP   m001; 
      L     #T_Quotient_1; // Ergebnis ausgeben
      T     #Q_Quotient_H; 
      L     #T_Quotient_0; 
      T     #Q_Quotient_L; 
      L     #T_Divident_2; 
      T     #Q_Modulo; 
 
END_FUNCTION

und da ich die Darstellung / Formatierung im AWL-Editor schöner finde, hier nochmal nicht als Quelle, sondern aus dem Editor rauskopiert:
Code:
      L     64                          // Initialisierung
      T     #T_Schleifenzaehler
      L     0
      T     #T_Divident_2
      L     #I_Divident_H
      T     #T_Divident_1
      L     #I_Divident_L
      T     #T_Divident_0
 
m001: NOP   0                           // Schleifeneinsprungpunkt
 
      L     #T_Divident_0               // Dividentregister eins links schieben
      RLDA  
      T     #T_Divident_0
      L     #T_Divident_1
      RLDA  
      T     #T_Divident_1
      L     #T_Divident_2
      RLDA  
      T     #T_Divident_2
      L     #I_Divisor
      <D    
      R     #T_Zwischenergebnis
      SPB   m002
      =     #T_Zwischenergebnis         // wird hier auf 1 gesetzt
      -D                                // und der Divident ...
      T     #T_Divident_2               // ... entsprechend vermindert
 
m002: L     0                           // Zwischenergebnis in das Ergebnisregister ...
      UN    #T_Zwischenergebnis
      SPB   m003
      L     DW#16#80000000
m003: RLDA                              // ... von rechts nach links reinschieben
      L     #T_Quotient_0
      RLDA  
      T     #T_Quotient_0
      L     #T_Quotient_1
      RLDA  
      T     #T_Quotient_1
 
      L     #T_Schleifenzaehler         // wiederholen
      L     1
      -I    
      T     #T_Schleifenzaehler
      SPP   m001
 
      L     #T_Quotient_1               // Ergebnis ausgeben
      T     #Q_Quotient_H
      L     #T_Quotient_0
      T     #Q_Quotient_L
      L     #T_Divident_2
      T     #Q_Modulo
 
Zuletzt bearbeitet:
nun hab ich mal meine Mittagspause damit verbracht, so eine Division zu stricken - hier mal Code, aber ungetestet!



Vielen vielen Dank!
Hab bis gerade versuch das ganze selbst zu entwerfen (allerdings bisher nur mit 32 Schleifendurchläufen), bis ich Deine Nachricht entdeckt hab.
Muss jetzt aber leider heim, in 45min is Abfahrt in Urlaub.
Habe ab jetzt bis 16.6 "Zwangsurlaub" und dann werde ich Dein Programm testen! Daheim kann ich es leider nicht ausprobieren.

Also vielen Dank nochmal und ich melde mich dann, wenn ich wieder in der Arbeit bin und das Programm getestet hab.

Liebe Grüße
Lisa
 
Zuviel Werbung?
-> Hier kostenlos registrieren
nun hab ich mal meine Mittagspause damit verbracht, so eine Division zu stricken - hier mal Code, aber ungetestet!

Code:
      L     64                          // Initialisierung
      T     #T_Schleifenzaehler
      L     0
      T     #T_Divident_2
      L     #I_Divident_H
      T     #T_Divident_1
      L     #I_Divident_L
      T     #T_Divident_0
 
m001: NOP   0                           // Schleifeneinsprungpunkt
 
      L     #T_Divident_0               // Dividentregister eins links schieben
      RLDA  
      T     #T_Divident_0
      L     #T_Divident_1
      RLDA  
      T     #T_Divident_1
      L     #T_Divident_2
      RLDA  
      T     #T_Divident_2
      L     #I_Divisor
      <D    
      R     #T_Zwischenergebnis
      SPB   m002
      =     #T_Zwischenergebnis         // wird hier auf 1 gesetzt
      -D                                // und der Divident ...
      T     #T_Divident_2               // ... entsprechend vermindert
 
m002: L     0                           // Zwischenergebnis in das Ergebnisregister ...
      UN    #T_Zwischenergebnis
      SPB   m003
      L     DW#16#80000000
m003: RLDA                              // ... von rechts nach links reinschieben
      L     #T_Quotient_0
      RLDA  
      T     #T_Quotient_0
      L     #T_Quotient_1
      RLDA  
      T     #T_Quotient_1
 
      L     #T_Schleifenzaehler         // wiederholen
      L     1
      -I    
      T     #T_Schleifenzaehler
      SPP   m001
 
      L     #T_Quotient_1               // Ergebnis ausgeben
      T     #Q_Quotient_H
      L     #T_Quotient_0
      T     #Q_Quotient_L
      L     #T_Divident_2
      T     #Q_Modulo




Hallo Perfektionist,

ich hätte noch ein paar Fragen zu Deinem Code:
Warum ist der Befehl "L DW#16#80000000" in m002 nötig?
Geht es hier nur darum, dass eine 1 nach links rotiert wird oder hat das einen anderen Grund?
Und warum sind folgende 2 Variablen nur WORD und nicht DWORD?
T_Divident_1 : WORD ;
T_Divident_0 : WORD ;

Ich habe vorhin das Programm getestet.
Für T_Divident_1 und T_Divident_0 habe ich DWORD statt WORD genommen, weil sonst nur 0 rauskam.
Nach dieser Änderung bekomme ich das Low-WORD richtig heraus, der Rest ist aber falsch.
Zum Beispiel erhalte ich 1A08, wenn das richtige Ergebnis 10F321A08 wäre, oder D40 wenn 30D40 richtig wäre.


Für die Berechnung habe ich den Low-Teil des 64 Bit Dividenden der Variablen "T_Divident_0" und den Hi-Teil der Variablen "T_Divident_1" zugewiesen.
Ja und den Divisor hab ich I_Divisor zugeordnet.

L 64 // Initialisierung
T #T_Schleifenzaehler

L 0
T #T_Divident_2

// L #I_Divident_H
L #erg_mul_HI // HI-Teil des Dividenden
T #T_Divident_1

// L #I_Divident_L
L #erg_mul_LO // LO-Teil des Dividenden
T #T_Divident_0

L #divisor
T #I_Divisor


Kannst du mir bitte nochmal helfen, warum das noch nicht richtig funktioniert?

Danke schonmal

Lisa
 
also, da hab ich wohl zu schnell geschrieben:
Code:
  T_Divident_1 : WORD ; 
  T_Divident_0 : WORD ; 
  T_Quotient_1 : WORD ; 
  T_Quotient_0 : WORD ;
das muss nicht nur zweimal, wie Du bereits richtig erkannt hast, sondern sogar viermal DWORD sein.

mit
Code:
      UN    #T_Zwischenergebnis
      SPB   m003
      L     DW#16#80000000
m003: RLDA                              // ... von rechts nach links reinschieben
wird das reinrotieren des Ergebnisbit in das Ausgaberegister vorbereitet.
 
also, da hab ich wohl zu schnell geschrieben:
Code:
  T_Divident_1 : WORD ; 
  T_Divident_0 : WORD ; 
  T_Quotient_1 : WORD ; 
  T_Quotient_0 : WORD ;
das muss nicht nur zweimal, wie Du bereits richtig erkannt hast, sondern sogar viermal DWORD sein.

mit
Code:
      UN    #T_Zwischenergebnis
      SPB   m003
      L     DW#16#80000000
m003: RLDA                              // ... von rechts nach links reinschieben
wird das reinrotieren des Ergebnisbit in das Ausgaberegister vorbereitet.



Danke, danke, danke!!!
Jetzt funktionierts richtig!

Hatte total vergessen Quotient_0 und Quotient_1 auch auf DWORD zu ändern.

Aber kannst Du mir noch erklären, warum man ausgerechnet 80.000.000 laden muss, um das rotieren ins Ausgaberegister vorzubereiten?


Liebe Grüße
Lisa
 
Hallo Perfektionist,
Deine Division funktioniert größtenteils wunderbar. Allerdings habe ich Probleme, wenn ich die Grenzwerte einsetze.
Meiner Meinung nach müsste es eigentlich auch mit diesen Werten noch funktionieren.

[(IN-b)*(c-d)] / (a-b) + d



Ich setze folgende Grenzwerte in die Formel ein:
IN: 2^30 -1
a: 2^30 -1
b: - 2^30
c: 2^31 -1
d: - 2^31



Mit diesen Werten bekomme ich für den Dividenden [(IN-b)*(c-d)]: 7FFFFFFE80000001


und für den Divisor (a-b): 7FFFFFFF



Nun sollte ich als Ergebnis der Division FFFFFFFF rausbekommen, ich bekomme aber leider nur 0 raus. Erst wenn ich meinen Divisor bis auf 67F3547 verkleinere, erhalte ich wieder ein richtiges Ergebnis.


Kannst Du mir vielleicht erklären, warum das mit den Grenzwerten nicht funktioniert?
 
Zuletzt bearbeitet:
Ich habe jetzt nicht jedes Detail gelesen, aber bist du dir sicher da nicht in ein Vorzeichenproblem zu rutschen mit deinem d=-2^31? Falls irgendwo noch 32-Bit Arithmetik verwendet sollte, dann knallt es.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Ich habe jetzt nicht jedes Detail gelesen, aber bist du dir sicher da nicht in ein Vorzeichenproblem zu rutschen mit deinem d=-2^31? Falls irgendwo noch 32-Bit Arithmetik verwendet sollte, dann knallt es.

Hallo,
glaube nicht, dass ich ein Vorzeichenproblem habe.
Bei der Differenz (c-d) reichen die 32 Bit nicht aus (zumindest nicht bei Grenzwerten). Deswegen habe ich das Ergebnis dieser Differenz auf einen Low-Teil (Word) und einen High-Teil (DINT) aufgeteilt. Somit habe ich 48Bit zur Verfügung und das muss eigentlich reichen.

Die Ergebnisse bis zur Division sind auch noch richtig, Dividend und Divisor werden richtig übergeben. Nur bei der Division gibt es dann irgendwie Probleme.
 
... Deswegen habe ich das Ergebnis dieser Differenz auf einen Low-Teil (Word) und einen High-Teil (DINT) aufgeteilt. Somit habe ich 48Bit zur Verfügung und das muss eigentlich reichen.

Die Ergebnisse bis zur Division sind auch noch richtig, Dividend und Divisor werden richtig übergeben. Nur bei der Division gibt es dann irgendwie Probleme.

Das ist genau das, was Hovonlo meinte ...
Der DINT wertet das Bit 2^31 als Vorzeichen aus. Das könnte das Problem sein. Ich würde also in dem DINT vermeiden, dieses Bit zu benutzen ...
Check das doch mal ...

Gruß
LL
 
Nachdem ja immer wieder Probleme auftauchen mit Zahlen >=31Bit hätte ich da ein paar Routinen zum Rechen mit 64 Bit Ganzzahlen

Es gibt Funktionen für:
- Multiplikation 32Bit * 32Bit vorzeichenlos mit 64 Bit Result
- Quadrat 32Bit vorzeichenlos mit 64 Bit Result
- Addition 64Bit + 64Bit vorzeichenbehaftet mit 64Bit Result
- Subtraktion 64Bit - 64Bit vorzeichenbehaftet mit 64Bit Result
- Multiplikation 64Bit * 64Bit vorzeichenbehaftet mit 128Bit Result
- Quadrat 64Bit vorzeichenbehaftet mit 128Bit Result (schneller als Multiplikation)
- Absolutwertbildung 64Bit
- Negierung 64Bit
- Division 64Bit / 64Bit vorzeichenbehaftet mit 64Bit Result und Rest

Das ganze ist selbst geschrieben und hat bisher funktioniert (Statistik-Anwendung mit Mittelwert & Standardabweichung)
 

Anhänge

  • Mathe_64.zip
    39,1 KB · Aufrufe: 105
Zurück
Oben