# Nach-Neujahrsrätsel



## Thomas_v2.1 (5 Januar 2019)

Die Maschine läuft soweit, aber jetzt muss doch noch eine Kleinigkeit in der SPS gerechnet werden.

Der Kunde möchte gerne noch eine Berechnung in der SPS gemacht haben, bei der der Wert einer Integer-Variable durch drei zu geteilt und das Ergebnis ausgegeben werden soll.

Mal wieder wurde um Geld zu sparen eine eher bescheidene CPU eingesetzt.
Im Gegensatz zur letzten Aufgabe wurde der CPU wenigstens eine Recheneinheit spendiert welche die Addition und die Subtraktion beherrscht.
Multiplikation oder Division allerdings leider nicht möglich.

Vorhanden ist aber der Schiebefehl. Eine Stelle nach rechts schieben teilt durch zwei. Aber lässt sich damit eine Zahl durch drei teilen?


----------



## JesperMP (7 Januar 2019)

Antwort:

```
[COLOR=#ffffff]Annähert Division durch 3.
* 0.33333

L EingangsWert
SHR 2
L IntegerWert
SHR 4
+I
L IntegerWert
SHR 6
+I
L IntegerWert
SHR 8
+I
L IntegerWert
SHR 10
+I
L IntegerWert
SHR 12
+I
L IntegerWert
SHR 14
+I
T AusgangsWert

ergibt Ausgangswert = Eingangswert * 0.33331298828125 = Eingangswert / 3.0001831166453030580479765610694[/COLOR]
```


----------



## Thomas_v2.1 (7 Januar 2019)

Das ist auch eine interessante Lösung Jesper, hat nur so ein kleines Problem mit der Genauigkeit.


----------



## Onkel Dagobert (7 Januar 2019)

Thomas,

muss der ganz 16Bit-Integerbereich abgedeckt werden? Beherrscht die bescheidene CPU auch 32Bit-Arithmetik?


----------



## Heinileini (7 Januar 2019)

Beherrscht sie vielleicht sogar Vergleiche?


----------



## Thomas_v2.1 (7 Januar 2019)

Ob 16 oder 32 Bit ist egal, was einfacher ist. Das Vorzeichen muss nicht beachtet werden, kann aber.

Vergleiche sind auch möglich.


----------



## Howard (8 Januar 2019)

```
[COLOR=#ffffff]// Man könnte ganz einfach zählen, wie oft man die 3 vom Ursprungswert subtrahieren kann (in diesem Fall ohne Berücksichtigung des Vorzeichens und ohne Optimierung des AWL Codes):
     L     0
     T     #varCount

Loop: L     #varInt
      L     3
      -I
      T     #varInt

      L     #varInt
      L     0
      <I
      SPB   Ende
      L     1
      L     #varCount
      +I
      T     #varCount

      L     #varInt
      L     0
      <I
      SPBN  Loop
Ende: NOP 0
      L     #varCount
      T     #varResult
// oder analog dazu, zähen wie oft ich eine 3 addieren kann...[/COLOR][COLOR=#d3d3d3]
 [/COLOR]
```


----------



## Heinileini (8 Januar 2019)

Howard schrieb:


> ```
> // Man könnte ganz einfach zählen, wie oft man die 3 vom Ursprungswert subtrahieren kann (in diesem Fall ohne Berücksichtigung des Vorzeichens und ohne Optimierung des AWL Codes):
> . . .
> // oder analog dazu, zähen wie oft ich eine 3 addieren kann...
> ```


Wie ist die AufgabenStellung denn nun gemeint?
Ich dachte hierum ginge es:


Thomas_v2.1 schrieb:


> Vorhanden ist aber der *Schiebefehl*. Eine Stelle nach rechts schieben teilt durch zwei. Aber lässt sich damit eine Zahl durch drei teilen?


----------



## JesperMP (8 Januar 2019)

Vielleicht sollte man spezifizieren  ob Schleifen erlaubt sind oder nicht.


----------



## Onkel Dagobert (8 Januar 2019)

Klassische Binärdivision := Schieben, Strichrechnung und Vergleichen?


----------



## PN/DP (8 Januar 2019)

Howard schrieb:


> ```
> // Man könnte ganz einfach zählen, wie oft man die 3 vom Ursprungswert subtrahieren kann
> ```


Dieser Algorithmus ist sehr einfach verstehbar und eine Schleife ist ja so schön schnell hingetippselt - allerdings ist der Algorithmus absolut nicht SPS-tauglich, weil man da z.B. bei 16 Bit signed INT im ungünstigsten Fall bis zu knapp 11000 Schleifen-Durchläufe braucht (wieviele Minuten die SPS bei 32 Bit DINT rechnet (falls sie nicht in Stop geht), mag ich mir gar nicht vorstellen). Da kann man sicherlich noch viel abkürzen, das ist aber trotzdem nicht das Wahre.

Besser sind Algorithmen, die höchstens soviele Schritte bzw. Schleifendurchläufe wie Bits erfordern. z.B. Rechnen wie man in der Schule die schriftliche Division gelernt hat, nur halt mit Dualzahlen - da braucht man nur Addition/Subtraktion und Bitschieben ("Binäre Division").

```
Division dezimal:      |  Division dual:
                       |
 109 : 3 = 36 Rest 1   |  11[COLOR="#008000"][B]01[/B][/COLOR]1[COLOR="#0000FF"][B]01[/B][/COLOR] : 11 = 1[COLOR="#008000"][B]00[/B][/COLOR]1[COLOR="#0000FF"][B]00[/B][/COLOR] Rest 1
- 9                    | -11
 --                    |  --
  19                   |  00[COLOR="#008000"][B]01[/B][/COLOR]1
  18                   |    -11
  --                   |     --
   1                   |     00[COLOR="#0000FF"][B]01[/B][/COLOR]
```

Ich vermute, Thomas hat eine Lösung im Auge, welche anstatt der Division (durch 3) eine Multiplikation mit dem Kehrwert (Reziproke) macht (mal 1/3 = binär 0.010101...) Da braucht man auch nur ein paar Addition/Subtraktion und Bitschieben (Hacker's Delight Fig. 10-14)

Falls die CPU 64 Bit Operationen inkl. Multiplikation beherrscht, dann gibt es auch noch schnelle Berechnungen mit großen "Magic numbers" (Hacker's Delight Fig. 10-46)

Harald


----------



## DeltaMikeAir (8 Januar 2019)

Danke Harald ( vor allem für das nicht sichtbare  )


----------



## PN/DP (8 Januar 2019)

Bitte  
Nachtrag: in der HD Second Edition sind es die Fig. 10-16 und 10-48

Harald


----------



## Thomas_v2.1 (8 Januar 2019)

Es gibt keine Beschränkung, außer dass keine Multiplikation und Division verwendet werden darf.

Ich habe zwar eine Lösung, aber das Prinzip stammt nicht von mir. Ausprogrammiert sieht es ohne Schleife ungefähr so aus wie das von Jesper, ich weiß aber nicht ob ich da selber drauf gekommen wäre. Es lässt sich sogar im Alltag anwenden, ist also nichts programmier-spezifisches.


----------



## LargoD (8 Januar 2019)

Um den Algorithmus zu zeigen hier ein Programm in SCL für 15 Bit ohne Vorzeichen mit Schleife.
Pro Schleifendurchlauf wird ein Bit geliefert und als Abfall am Ende der Divisionsrest.

```
[COLOR=#ffffff]FUNCTION FC2 : INT
VAR_INPUT
  IN:INT;
END_VAR
VAR_OUTPUT
  MOD_3:INT;
END_VAR
VAR_TEMP
  Subtr : INT;
  Resultat : WORD;
  Divident : INT;
END_VAR
BEGIN
  Divident := IN;
  Subtr :=  24576; // 8192*3
  Resultat := 0;
  REPEAT
    Resultat := SHL(IN := Resultat,N := 1);
    IF Divident >= Subtr THEN
      Divident := Divident - Subtr;
      Resultat := INT_TO_WORD(WORD_TO_INT(Resultat) + 1);
    END_IF;
    Subtr := WORD_TO_INT(SHR(IN := INT_TO_WORD(Subtr), N := 1));
  UNTIL Subtr <3 END_REPEAT;
  MOD_3 := Divident;
  FC2 := WORD_TO_INT(Resultat);
END_FUNCTION
[/COLOR]
```
Wer Lust hat, kanns ja mal in AWL formulieren. Dann gehts auch leichter für 16 Bit
Gruß
Erich


----------



## Transistorfips (9 Januar 2019)

Hier eine Variante in KOP die gänzlich auf subtrahieren und schieben verzichtet.


----------



## PN/DP (9 Januar 2019)

Transistorfips schrieb:


> Hier eine Variante in KOP die gänzlich auf subtrahieren und schieben verzichtet.


1: Hmm, ich glaube Dein Programm liefert falsche Ergebnisse. Teste mal Dein Programm mit den Dividenden 1, 2, 3 und 4.

2: Teste mal Dein Programm mit den Dividenden 32766 und 32767
Hast Du eine reale CPU zum testen? Welche genau? Wie lange braucht da Dein Programm?

PS: Weißt Du, wofür es die VKE-Linie und die EN-Eingänge der Boxen bei KOP gibt? Damit man keine Sprünge braucht. Ohne JMP kann man mit nur 2 Netzwerken auskommen (NW1: Initialisierung, NW2: Zähler)

Harald


----------



## JesperMP (9 Januar 2019)

Mein Vorschlag liefert auch falsche Ergebnisse bei kleine Werte.
Es soll genannt werden, das auch Siemens S7 eigene Integer-Division falsche Ergebnisse liefert.
Integer Divison in S7-300 ergibt:
0/3 = 0
1/3 = 0
2/3 = 0 (sollte mit korrekten Rundung 1 sein)
3/3 = 1
4/3 = 1
5/3 = 1 (sollte mit korrekten Rundung 2 sein)
usw.
Ob es bei S7-1500 besser funktioniert habe ich nicht getestet.

Der Aufgabe ist interessant. Aber vielleicht ist die Konklusion wie viel Glück wir haben, dass heute problemlos mit Fliesskomma gerechnet werden kann.


----------



## PN/DP (9 Januar 2019)

JesperMP schrieb:


> Integer Divison in S7-300 ergibt:
> 0/3 = 0
> 1/3 = 0
> 2/3 = 0 (sollte mit korrekten Rundung 1 sein)


Das ist richtig so. Ganzzahl-Division rundet nie.
2/3 = 0 Rest 2
Das ist auch so in der Step7-Hilfe dokumentiert.

Harald


----------



## DeltaMikeAir (9 Januar 2019)

> 2/3 = 0 (sollte mit korrekten Rundung 1 sein)





> 5/3 = 1 (sollte mit korrekten Rundung 2 sein)



Der Windows Calculator liefert im "Programmierermodus" die gleichen von dir genannten Werte. Ganzzahldivisionen werden
nicht gerundet.


----------



## JesperMP (9 Januar 2019)

Da habe ich mir geirrt. Es ist tatsächlich so dass in fast alle Programmiersprachen wird bei Ganzzahldivision nicht gerundet. Und das war wohl die Aufgabe.

Ich habe mir eine Aufgabe in den realen Welt vorgestellt, wo man eine Berechnung habe aber nur mit Ganzzahlen arbeiten kann (das war so bei Siemens S5 - ausser bei die Grösste CPUs).
z.B ein Dossieraufgabe mit ein Anzahl Düsen der pro Düse 100 Liter/Std liefert, und man muss z.B so nah wie möglich an 290 Liter/Std kommen. Wenn man den Anzahl von Düsen berechnen will durch Division von den Sollwert durch Menge pro Düse, dann sind 3 Düsen (300 Liter/Std) korrekter als 2 Düsen (200 Liter/Std).


----------



## PN/DP (9 Januar 2019)

Hier in der Aufgabenstellung geht es darum, daß die bescheidene CPU keinen Ganzzahl-Divisionsbefehl hat (/I, DIV_I). Nun soll mit anderen Befehlen eine Division realisiert werden, die genau die gleichen Ergebnisse wie der nicht vorhandene Ganzzahl-Divisionsbefehl liefert. 
(ob die Ganzzahl-Division die richtige Lösung für das technische Problem ist, ist nicht Thema der Aufgabenstellung)

Harald


----------



## mek_meik (9 Januar 2019)

Ist das jetzt schon gelöst? 

Meine Idee wäre zu gucken ob die Zahl zwischen 3 und 30 oder 30 und 300 oder 300 und 3000....usw liegt, demnach fängt das Ergebnis dann mit 10000,1000,100,10 oder 1 an. Die dann vom Wert abziehen und das dann erneut prüfen. Das Ergebnis daraus dann zum ersten Ergebnis addieren.

Edit:
Ich sehe gerade dass ich wohl das gleiche mache wie Howard, nur das ich nicht immer 3 subtrahiere sondern die höchstmögliche 10er Potenz von 3 um Schleifendurchläufe zu reduzieren.


----------



## Heinileini (9 Januar 2019)

@mek_meik
Ich finde, Du denkst zu sehr in DezimalZahlen. Die Lösung von LargoD in #15 kommt Deinem LösungsWeg nahe, jedoch ist sie dem BinärSystem angepasst.


----------



## Transistorfips (9 Januar 2019)

PN/DP schreibte Heute, 09:49:
>Hast Du eine reale CPU zum testen? Welche genau? Wie lange braucht da Dein Programm?
CPU ist ne 1212C, Arbeitsspeicher 75KB, 6ES7 212-1BE40-0XB0 aus nem 1200er Starterkit.
Simulator-Rack steht versteckt hinterm Schlafzimmerschrank.

>1: Hmm, ich glaube Dein Programm liefert falsche Ergebnisse. Teste mal Dein Programm mit den Dividenden 1, 2, 3 und 4.
>2: Teste mal Dein Programm mit den Dividenden 32766 und 32767
32767/32767=1 Ergebnis ok; Dauer: zu schnell um zu ermitteln
10000/1=10000 Ergebnis ok; Dauer: ca. 6Sek
32764/4=8191 Ergebnis ok; Dauer: ca. 4Sek
9999/3=3333 Ergebnis ok; Dauer: ca. 2Sek
Die Ergebnisse entsprechen den Schleifendurchläufen

Jetzt die böse Division durch Null testen:
1000/0= Crasht, Schleife läuft unendlich


Und jetzt mal mit nem Ergebnis ohne glatte Zahl:
334/3=112; Wie erwartet ein Schleifendurchlauf zu viel. Aber so ist das halt mit den Integern...
32767/3= Oha! Overflow, Schleifenzähler stoppt nicht; Du hattest recht.
32766/4=16383 Overflow

Wenn beim letzten Durchlauf der Divisor größer ist als der Rest (Modulo) geht's schief.


Man könnte das ganze mit folgendem Lernziel umschreiben: Programme stets mit Grenzfällen testen um vom Kunden nach Feierabend keine blöden Telefonate zu erhalten...


----------



## Thomas_v2.1 (9 Januar 2019)

Die Rechenlösung mit den Dualzahlen gefällt mir wirklich gut, und ist auch einigermaßen schnell.

Was mich eigentlich auf dieses Rätsel gebracht hat war das folgende Video, mit einer Variante die bisher hier noch nicht gezeigt wurde:

https://www.youtube.com/watch?v=NinrTW1Bx2Y&feature=youtu.be&t=68

In AWL komprimiert auscodiert sieht es recht lustig aus. Da würde nie jemand auf die Idee kommen, dass dort durch drei geteilt wird.


----------



## PN/DP (10 Januar 2019)

Transistorfips schrieb:


> >1: Hmm, ich glaube Dein Programm liefert falsche Ergebnisse. Teste mal Dein Programm mit den Dividenden 1, 2, 3 und 4.
> >2: Teste mal Dein Programm mit den Dividenden 32766 und 32767


Ich meinte:

1/3 = ? (soll: 0 (Rest 1))
2/3 = ? (soll: 0 (Rest 2))
3/3 = ? (soll: 1 (Rest 0))
4/3 = ? (soll: 1 (Rest 1))
32767/3 = ? (soll: 10922 (Rest 1))
der größte mögliche INT: da rennt Dein Programm leider mehrmals durch den gesamten INT-Bereich
32766/3 = ? (soll: 10922 (Rest 0))
der größte durch 3 teilbare INT, wo Dein Programm noch funktioniert, mit den meisten Durchläufen (sozusagen fast "worst case"): wie lange dauert das auf der realen CPU? (PLCSIM V5.4 braucht ca. 110s)

Harald


----------



## PN/DP (10 Januar 2019)

Binäre Division zweier n-Bit Zahlen a und b (q = a/b) nach www.informatik.uni-ulm.de/ni/Lehre/SS01/TI/folien/arithC2.pdf 

in jedem Schritt wird Divisor b testweise vom Dividenden a subtrahiert:
qi = 1, falls a–b >= 0 (positiv)
qi = 0 und Korrektur durch a = a + b, falls a–b < 0 (negativ)
mit einem 2n-Bit Register q, einem n-Bit Addierer/Subtrahierer
nach n Schritten befindet sich der Quotient q in qL, der Rest in qH
in SCL für 15 Bit ohne Vorzeichen mit Schleife

```
FUNCTION FC3 : INT
[COLOR="#008000"]// q = a/b , b = 3 , n = 16 Bit ! nur positive a (0..32767) ![/COLOR]
VAR_INPUT
  a : INT; [COLOR="#008000"]//Dividend[/COLOR]
END_VAR
VAR_TEMP
  r32 : DWORD; [COLOR="#008000"]//32 Bit Register[/COLOR]
  q AT r32 : STRUCT
    H : INT; [COLOR="#008000"]//Divisionsrest[/COLOR]
    L : INT; [COLOR="#008000"]//Quotient[/COLOR]
  END_STRUCT;
  i : INT;
END_VAR
BEGIN                                      

   q.H := 0; q.L := a;                     [COLOR="#008000"]// q = (qH, qL) = (0, a)[/COLOR]

   FOR i := 0 TO 15 DO [COLOR="#008000"]//n = 16 Bit[/COLOR]        [COLOR="#008000"]// FOR i = 0 TO n-1 {[/COLOR]
     r32 := SHL(IN := r32, N := 1);        [COLOR="#008000"]//   shift LEFT (qH, qL) BY 1[/COLOR]
     q.H := q.H - 3;                       [COLOR="#008000"]//   qH = qH - b[/COLOR]

     IF (r32 AND 16#8000_0000) = 0 THEN    [COLOR="#008000"]//   IF (q[SUB]2n-1[/SUB] = 0)  //entspricht: a–b >= 0 (positiv)[/COLOR]
       r32 := r32 OR 1;                    [COLOR="#008000"]//     q[SUB]0[/SUB] = 1[/COLOR]
     ELSE                                  [COLOR="#008000"]//   ELSE[/COLOR]
[COLOR="#008000"]//     r32 := r32 AND NOT DWORD#1;         //     q[SUB]0[/SUB] = 0        //nicht nötig: wg. SHL schiebt 0 in q[SUB]0[/SUB][/COLOR]
       q.H := q.H + 3;                     [COLOR="#008000"]//     qH = qH + b[/COLOR]
     END_IF;
   END_FOR;                                [COLOR="#008000"]// }[/COLOR]

   FC3 := q.L; [COLOR="#008000"]//RET_VAL: qL ist der Quotient[/COLOR]

END_FUNCTION
```

Harald


----------



## LargoD (10 Januar 2019)

Man beachte die trickreiche (um nicht zu sagen hinterlistige) Doppel-Verwendung von r32.
Gruß
Erich


----------



## Thomas_v2.1 (10 Januar 2019)

Das ist meine Umsetzung des Prinzips aus dem Youtube-Video in AWL:

```
L     #In
      SRW   1
      L     #In
      TAK   
      -I    
      SRW   1
      L     #In
      TAK   
      -I    
      SRW   1
      L     #In
      TAK   
      -I    
      SRW   1
      L     #In
      TAK   
      -I    
      SRW   1
      L     #In
      TAK   
      -I    
      SRW   1
      L     #In
      TAK   
      -I    
      SRW   1
      L     #In
      TAK   
      -I    
      SRW   1
      L     #In
      TAK   
      -I    
      SRW   1
      L     #In
      TAK   
      -I    
      SRW   1
      L     #In
      TAK   
      -I    
      SRW   1
      L     #In
      TAK   
      -I    
      SRW   1
      L     #In
      TAK   
      -I    
      SRW   1
      L     #In
      TAK   
      -I    
      SRW   1
      L     #In
      TAK   
      -I    
      SRW   1
      L     #In
      TAK   
      -I    
      SRW   1
      T     #Out
```

In SCL und mit Schleife ist das etwas übersichtlicher:

```
tmp := WORD_TO_INT(SHR(IN := INT_TO_WORD(IN), N := 1));
FOR i := 1 TO 15 DO
    tmp := WORD_TO_INT(SHR(IN := INT_TO_WORD(IN - tmp), N := 1));
END_FOR;
OUT := tmp;
```


----------



## Transistorfips (10 Januar 2019)

@Harald:
Ich habe das eben mit meiner rudimentären Erfahrung in SCL einen FC geschrieben. Wenn ich zB. 'a' 50 zuweise dann bekomme ich auch 50 als Ergebnis.
Du schiebst r32. So wie es aussieht besteht aber kein Zusammenhang zwischen r32 und den Strukturelementen H und L. 

Ahhh!
Kann es sein, daß mit der Zeile

```
q AT r32 : STRUCT
```
r32 (4 x 8Byte) auf den Speicherplatz von q (4 x 8Byte) mit dem Schlüsselwort 'AT' zugewiesen wird (ich kenn das so ähnlich von der Programmiersprache C)?

Ich benutze fast nie SCL in TIA und hab wirklich kaum Erfahrung mit dem Syntax...


----------



## PN/DP (10 Januar 2019)

LargoD schrieb:


> Man beachte die trickreiche (um nicht zu sagen hinterlistige) Doppel-Verwendung von r32.


... und ganz besonders die Doppel-Verwendung von q.L 

Harald


----------



## Thomas_v2.1 (10 Januar 2019)

Ich überlege noch wie man die Variante von Jesper genauer hinbekommt. Denn die Reihe 1/4^n für n=1 bis n=unendlich besitzt den Grenzwert 1/3, das ist ja das was benötigt wird.
Leider summieren sich die Reste der Division bzw. die herausgeschobenen Bits auf, und darum wird es dann ungenau. Man müsste diese Rest-Bits einigermaßen elegant mitführen, z.B. in Anzahl 16384stel oder etwas in der Art, und denn die Reste zur Gesamtsumme hinzuaddieren.


----------



## PN/DP (10 Januar 2019)

Transistorfips schrieb:


> Ahhh!
> Kann es sein, daß mit der Zeile
> 
> ```
> ...


Ja. Genau. Das ist eine Union, damit das 32-Bit-DWORD insgesamt und das H-Word und das L-Word einzeln angesprochen werden kann.

Harald


----------



## PN/DP (10 Januar 2019)

Thomas_v2.1 schrieb:


> Ich überlege noch wie man die Variante von Jesper genauer hinbekommt. Denn die Reihe 1/4^n für n=1 bis n=unendlich besitzt den Grenzwert 1/3, das ist ja das was benötigt wird.


Das Annähern war auch erst meine Idee, doch es erschien mir zu aufwendig, die Korrektur der Ungenauigkeiten zu durchdenken.

Harald


----------



## PN/DP (11 Januar 2019)

32 Bit signed Division (durch 3) mittels Multiplikation mit dem Kehrwert (Reziproke) (mal 1/3 = binär 0.010101...)
Hacker's Delight PDF 10-17 (Fig 10-14)

http://www.hackersdelight.org/hdcodetxt/divsc.c.txt 

```
int divs3b(int n) {
   int q, r;

   n = n + (n>>31 & 2);         [COLOR="#008080"]// Add 2 if n < 0.[/COLOR]
   q = (n >> 2) + (n >> 4);     [COLOR="#008080"]// q = n*0.0101 (approx).[/COLOR]
   q = q + (q >> 4);            [COLOR="#008080"]// q = n*0.01010101.[/COLOR]
   q = q + (q >> 8);
   q = q + (q >> 16);
   r = n - q*3;                 [COLOR="#008080"]// 0 <= r <= 14.[/COLOR]
   return q + (11*r >> 5);      [COLOR="#008080"]// Returning q + r/3.[/COLOR]
[COLOR="#008080"]// return q + (5*(r + 1) >> 4);         // Alternative 1.[/COLOR]
[COLOR="#008080"]// return q + ((r + 5 + (r << 2)) >> 4);// Alternative 2.[/COLOR]
}
```

Der Algorithmus benötigt signed Shift Right, das gibt es nicht in SCL, deshalb hier in AWL.
Ohne Kommentare kommt man wohl auch hier nicht drauf, dass durch 3 geteilt wird.

```
FUNCTION FC 6 : DINT
TITLE =DINT-Division durch 3: * 1/3
[COLOR="#008000"]//Hacker's Delight 10-17 (Fig 10-14)[/COLOR]
[COLOR="#008000"]//q = a/3 = a * 1/3 , 32 Bit signed[/COLOR]
AUTHOR: PN_DP

VAR_INPUT
  a : DINT ; [COLOR="#008000"]//Dividend[/COLOR]
END_VAR
VAR_TEMP
  n : DINT ; [COLOR="#008000"]//Dividend[/COLOR]
  q : DINT ; [COLOR="#008000"]//Quotient[/COLOR]
  r : DINT ; [COLOR="#008000"]//Divisionsrest[/COLOR]
END_VAR
BEGIN
[COLOR="#008080"]// n = a;
// n = n + (n>>31 & 2);         // Add 2 if n < 0.[/COLOR]
      L     #a;
      PUSH  ;
      SRD   31;
      SLD   1;
      +D    ;
      T     #n;
[COLOR="#008080"]// q = (n >>  2) + (n >> 4);    // q = n*0.0101 (approx).[/COLOR]
      SSD   2;
      PUSH  ;
      SSD   2;
      +D    ;
[COLOR="#008080"]// q = q + (q >> 4);            // q = n*0.01010101.[/COLOR]
      PUSH  ;
      SSD   4;
      +D    ;
[COLOR="#008080"]// q = q + (q >> 8);[/COLOR]
      PUSH  ;
      SSD   8;
      +D    ;
[COLOR="#008080"]// q = q + (q >> 16);[/COLOR]
      PUSH  ;
      SSD   16;
      +D    ;
      T     #q;
[COLOR="#008080"]// r = n - q*3;                 // 0 <= r <= 14.[/COLOR]
      PUSH  ;
      PUSH  ; [COLOR="#008000"]//falls S7-CPU mit 4 AKKUs oder PLCSIM[/COLOR]
      +D    ;
      +D    ;
      L     #n;
      TAK   ;
      -D    ;
[COLOR="#008080"]// return q + (5*(r + 1) >> 4); // Returning q + r/3.[/COLOR]
      +     1;
      PUSH  ;
      SLD   2;
      +D    ;
      SRD   4;
      L     #q;
      +D    ;
      T     #RET_VAL;
END_FUNCTION
```

Harald


----------



## PN/DP (11 Januar 2019)

Transistorfips schrieb:


> Ich habe das eben mit meiner rudimentären Erfahrung in SCL einen FC geschrieben. Wenn ich zB. 'a' 50 zuweise dann bekomme ich auch 50 als Ergebnis.
> [...]
> 
> ```
> ...


Das "AT" aus dem SCL Code ins TIA bringen: Die Baustein-Deklarationen kann man nicht einfach als Text aus dem Programmtext (SCL-Quelle) kopieren, sondern muß die (A) im SCL-Editor oben zeilenweise zusammenklicken oder (B) die SCL-Quelle importieren.

(A) im Deklarationsbereich unter Temp zunächst das "r32 : DWord" deklarieren.
Direkt in der nächsten Zeile darunter bei Name "q" eingeben, bei Datentyp "AT" eingeben *und Enter drücken*, dann bei Datentyp "Struct" eingeben. siehe das angehängte Bild
(B) im Projektbaum: "Externe Quellen > Neue externe Datei hinzufügen" eine *.scl-Datei zufügen
dann auf die eingefügte Datei "Rechsklick > Bausteine aus Quelle generieren"
Im Anhang der FC als SCL-Quelle für TIA: BinDiv3_Strey.scl.txt
Zum Einfügen als externe Quelle die Dateiendung .txt entfernen: BinDiv3_Strey.scl.txt --> BinDiv3_Strey.scl
Den Programmcode nach BEGIN kann man auch einfach in den SCL-Editor in den Codebereich kopieren, allerdings ist der TIA-SCL-Editor ziemlich "eigen", der übernimmt grundsätzlich nichts so wie man will, sondern fummelt da überall dran rum, z.B. bei Text aus der Zwischenablage "einfügen" werden die Einrückungen nach den Vorstellungen des Editor geändert, Leerzeichen werden eingefügt oder entfernt, mehrere Anweisungen in einer Zeile werden in mehrere Zeilen zerlegt, Groß/Kleinschreibungen werden angepasst, ...
(Hat der Editor eigentlich auch eine Statusanzeige, in welcher Zeile/Spalte sich der Cursor befindet? In TIA V13 finde ich nichts)

Harald


----------



## hucki (11 Januar 2019)

PN/DP schrieb:


> ... in den Codebereich kopieren, allerdings ist der TIA-SCL-Editor ziemlich "eigen", der übernimmt grundsätzlich nichts so wie man will, sondern fummelt da überall dran rum, z.B. bei Text aus der Zwischenablage "einfügen" werden die Einrückungen nach den Vorstellungen des Editor geändert, Leerzeichen werden eingefügt oder entfernt, mehrere Anweisungen in einer Zeile werden in mehrere Zeilen zerlegt, Groß/Kleinschreibungen werden angepasst, ...


Dieses Verhalten kann in den Einstellungen deaktiviert werden:






Auch bei anschließendem Reaktivieren bleibt bestehender Text in seinem Format, solange nichts daran geändert wird.





PN/DP schrieb:


> ... (Hat der Editor eigentlich auch eine Statusanzeige, in welcher Zeile/Spalte sich der Cursor befindet? In TIA V13 finde ich nichts)


In der Statuszeile des Editors unten rechts vor der Größeneinstellung, Line und Column:





(Deinen Text mit obiger Einstellung eingefügt, Variablen noch nicht angelegt.
Wie man im Bild sieht, können die Zeilennummern auch angezeigt werden.)


PS:
Alle Angaben für V15, sollte IMHO aber auch bei V13 schon so gewesen sein.


----------



## PN/DP (11 Januar 2019)

OK, das mit dem Abschalten der "Smarten" Einrückungen und Umformatierungen funktioniert.

Die Anzeige von Line und Column in der Statuszeile gibt es in meinem TIA V13 aber noch nicht, weder im SCL- noch im AWL-Editor :roll: Eigentlich unglaublich, daß solche essentiellen Funktionen erst nachgereicht werden, wenn den TIA-Programmierern keine anderen optischen Spielereien mehr einfallen (oder war der gelbe Zettel verloren gegangen?)  Die Anzeige der aktuellen Cursorposition wird im PDF "TIA Portal V15 Neue Funktionen" Seite 32 als Innovation angepriesen. (Hat der AWL-Editor das nun auch?)

Harald


----------



## DeltaMikeAir (11 Januar 2019)

> "als Innovation angepriesen."



Ja, da wurde hier irgendwo und irgendwann schon mal drüber geschmunzelt. Zu Commodore Zeiten konnten das ja schon die meißten Editoren ( wenn nicht alle?? )


----------



## Thomas_v2.1 (11 Januar 2019)

Ich habe das Verfahren von Jesper mal so angepasst, dass die Reste der Division in Form von 65536steln aufsummiert werden, und anschließend zu Gesamtwert hinzu addiert werden.
Sieht allerdings nicht sehr schön aus, außerdem gibt es einen Korrekturwert um den die summierten Reste angehoben werden müssen, um diese passend aufzurunden.
Wobei die Schleife an sich nur 8 Durchläufe besitzt.


```
FUNCTION FC200 : VOID

VAR_INPUT
    IN : INT;
END_VAR

VAR_OUTPUT
    OUT : INT;
END_VAR

VAR_TEMP
    i : INT;
    r : DINT;
    g : DINT;
    tmp : DWORD;
    mask : DWORD;
END_VAR

BEGIN
r := 0;
g := 0;
mask := 16#ffff;

FOR i := 16 TO 2 BY -2 DO
    g := g + (WORD_TO_DINT(SHR(IN := INT_TO_WORD(IN), N := i)));
    // Rest der Division erfassen und in 65536stel aufsummieren
    tmp := SHL(IN := INT_TO_DWORD(IN) AND mask, N := 16 - i);
    r := r + DWORD_TO_DINT(tmp);
    mask := SHR(IN := mask, N := 2);
END_FOR;
// Aufsummierte Reste von 65536stel in ganze Zahl, dabei Anpassung zur Aufrundung + 1/3 = 65536 / 3 = 21845.
// Der größte Fehler bei dem noch nicht "aufgerundet" werden darf entsteht bei 2, dann ist der Rest 2/3.
r := DWORD_TO_DINT(SHR(IN := DINT_TO_DWORD(r + 21845), N := 16));
OUT := DINT_TO_INT(g + r);

END_FUNCTION
```


----------



## Heinileini (12 Januar 2019)

Thomas_v2.1 schrieb:


> Ich habe das Verfahren von Jesper mal so angepasst . . .


. . . und ich so:

```
L    #IN   // Division einer positiven DINT-Zahl (aber: Werte 0 bis 32767) durch 3
SLD  16
T    #TMP
SRD  1
L    #TMP
SRD  3
-I   
L    #TMP
SRD  5
-I   
L    #TMP
SRD  7
-I   
L    #TMP
SRD  9
-I   
L    #TMP
SRD  11
-I   
L    #TMP
SRD  13
-I   
L    #TMP
SRD  15
-I   
SRD  16
T    #OUT
```
Jesper multipliziert mit einem NäherungsWert von 1/3 nämlich 
0.010101010101010 und ich auch, aber mit einem anderen nämlich 0.1 - 0.001010101010101 =
0.010101010101011.
1/3 stellt im DezimalSystem und im BinärSystem einen unendlichen periodischen Bruch dar.
Jespers Approximation ist zu klein und meine ist zu gross, um "genau" 1/3 darzustellen.
​Das führt einerseits dazu, dass Jespers Ergebnisse ein winziges Bisschen *zu klein* sind und durch das *Abrunden* (= die "Beschränkung" auf 16 Bit) alle Divisionen, die den Rest 0 hätten, ein zu kleines Ergebnis liefern.
Das führt andererseits dazu, dass meine Ergebnisse ein winziges Bisschen *zu gross* sind und durch das *Abrunden* (= die "Beschränkung" auf 16 Bit) alle Divisionen, auch die, die den Rest 0 hätten, zum passenden Ergebnis kommen.

Vielsten Dank für die vielen interessanten Beiträge!

Gruss, Heinileini

PS: 
Man stelle sich vor, es wäre nicht möglich, eine Division durch Addition bzw. Subtraktion und durch Schieben der Bits im Akku zu realisieren . . .
Gäbe es dieses Forum dann gar nicht (weil es keine Computer gäbe)? Oder hätten dann 99% der Beiträge "Divisions-WorkArounds" zum Thema?

PPS:
Die "VideoLösung" ist gar nicht so unraffiniert, aber im EndEffekt zu aufwändig.

Edit:
Habe meinen Code jetzt so erweitert, dass vorab die EingangsVariable um 16 Bit nach links und abschliessend das Ergebnis um 16 Bit nach rechts geschoben wird. Ich hoffe, der jetzige Versuch der Umsetzung in AWL kommt dem näher, was in der Excel-Simulation so gut aussah. Das werde ich in Excel noch testen . . .


​


----------



## Thomas_v2.1 (12 Januar 2019)

Heinileini, in der SPS gibt dein Code an OUT immer Null zurück.
Ich hatte auch ein paar Versuche mit Excel gemacht, aber da Excel immer Gleitkomma rechnet gibt es da schon Unterschiede.


----------



## Heinileini (12 Januar 2019)

Thomas_v2.1 schrieb:


> Heinileini, in der SPS gibt dein Code an OUT immer Null zurück.


Hallo Thomas, ich hatte vergessen, zu betonen, dass #Tmp 32 Bit haben muss.
Aber es hilft alles nichts - das Ergebnis ist niederschmetternd und voll daneben.
Das kommt davon, wenn man versucht, wider besseren Wissens einen Weg zu beschreiten, der eigentlich nicht zur Aufgabenstellung passt.
Die Multiplikation mit dem Kehrwert des Divisors muss einfach abenteuerlich werden, wenn der Divisor keine reine ZweierPotenz ist.
Da kann man noch so viele Bits hinzunehmen, um die Genauigkeit zu verbessern . . .

Mein Favorit sieht in der jetzigen Excel-VBA-TestVersion so aus:

```
Function Divi(ByVal x1, ByVal x2)
xRest& = Abs(x1)
xDivisor& = Abs(x2)
If xDivisor& = 0 Then
    Divi = "Div/0"
Else
    xBitPos& = 1
    Do While xRest& > xDivisor&
        xBitPos& = xBitPos& + xBitPos&    ' << ShiftLeft
        xDivisor& = xDivisor& + xDivisor& ' << ShiftLeft
        Loop
    xQuotient& = 0
    Do While xRest& > 0 And xBitPos& > 0
        If xRest& >= xDivisor& Then
            xRest& = xRest& - xDivisor&
            xQuotient& = xQuotient& + xBitPos&
            End If
        xBitPos& = Int(xBitPos& / 2)   ' ShiftRight >>
        xDivisor& = Int(xDivisor& / 2) ' ShiftRight >>
        Loop
    Divi = xQuotient& & " " & xRest&
End Function
```
Dividend und Divisor müssen positiv sein und alle Variablen sinnvollerweise gleichen Typs (alle INT oder alle DINT oder . . .). Der Divisor ist frei wählbar (zweiter Parameter) und die Anzahl der SchleifenDurchläufe hält sich in Grenzen.
Das Ergebnis ist ein String, der den Quotienten und den Rest enthält, getrennt durch ein Leerzeichen.
Die Funktion enthält notgedrungen Divisionen, allerdings nur durch 2, um die RechtsSchiebeBefehle zu "realisieren" (übrigens ein MusterBeispiel dafür, dass VBA bei Divisionen von Ganzzahlen rundet und nicht abschneidet - deshalb INT(x/2)).

Gruss, Heinileini


----------



## PN/DP (12 Januar 2019)

Man muß die Ungenauigkeiten nicht aufsummieren, man kann sie auch korrigieren, indem man am Ende das vorläufige Ergebnis (wie bei einer Kontrollrechnung) mit 3 multipliziert (q + q + q), vom Ausgangswert abzieht, und den verbleibenden kleinen Rest mit einer Näherungsformel mit Zweierpotenz-Division durch 3 dividiert (z.B. 11*r/32, oder 5*(r+1)/16) und zum vorläufigen Ergebnis addiert. So wie hier in dem Algorithmus von Hacker's Delight.


PN/DP schrieb:


> 32 Bit signed Division (durch 3) mittels Multiplikation mit dem Kehrwert (Reziproke) (mal 1/3 = binär 0.010101...)


PS: Der Algorithmus funktioniert für alle 32-Bit signed Integer. Was mich dabei fasziniert: am Anfang 2 addieren, falls der Dividend negativ ist. 

Harald


----------

