Integer-Arithmetik

Die Integer-Arithmetik behandelt die Grundoperationen Addition, Subtraktion, Multiplikation und Division innerhalb eines Prozessors. Integer-Zahlen werden in Prozessoren in sogenannten Registern verarbeitet. Register sind Speicherzellen, welche einen einzigen Wert speichern. Zu Beginn der Heimcomputer-Ära verarbeiteten Register 8 oder 16 Bit, wenig später konnten jedoch auch 32 Bit-Werte verarbeitet werden. Die 32-Bit-Ära hielt sich relativ lange, womit sich ein Quasi-Standard von 32 Bits für den überall verwendeten int-Typ etablierte. Heutige Prozessoren besitzen 64-Bit-Register.

In einem einzelnen Register können verschiedene Integer-Grössen gespeichert und verarbeitet werden, die eingebauten Grundoperationen erwarten jedoch normalerweise, dass die Register der beiden Operanden dieselbe Grösse, also dieselbe Anzahl binärer Ziffern speichern.

Die vier Grundoperationen können binär grundsätzlich genau gleich behandelt werden, wie die dezimalen Rechnungen, welche in der Grundschule gelernt werden. Nur müssen in einem Prozessor Rechnungen mittels des Binärsystems abgebildet und mithilfe logischer Verknüpfungen durchgeführt werden. Auf dieser Seite wird der Einfachheit halber nur die Ganzzahlcodierung mittels des Zweierkomplements betrachtet.

Addition und Subtraktion

Bei der Addition zweier Zahlen mit gleich vielen Ziffern (egal in welchem Zahlensystem) kann das Resultat stets mit gleich vielen Ziffern plus einer zusätzlichen Ziffer gespeichert werden. Die zusätzliche Ziffer wird zu Deutsch Übertrag oder auf Englisch Carry genannt. Beispielsweise ergibt 7 + 8 = 15, also die Summe 5 mit Übertrag 1. Bei binären Ziffern sind folgende vier Kombinationen möglich:

Addition       Sum             Remainder
------------------------------------------
0 + 0 =  0     0 XOR 0 = 0     0 AND 0 = 0
0 + 1 =  1     0 XOR 1 = 1     0 AND 1 = 0
1 + 0 =  1     1 XOR 0 = 1     1 AND 0 = 0
1 + 1 = 10     1 XOR 1 = 0     1 AND 1 = 1

Aus dieser Tabelle wird ersichtlich, dass die Summe als XOR-Verknüpfung und der Übertrag als AND-Verknüpfung der beiden Operanden nachgebildet werden kann. Ein Prozessor führt diese logischen Verknüpfungen für alle binären Ziffern gleichzeitig aus. Falls nach der Verknüpfung ein Übertrag bleibt, so wird dieser in einem weiteren Schritt der jeweils nächst höheren Ziffer mittels einer Shift-Left-Operation (SHL) hinzuaddiert.



Decimal 14
Decimal 11













Sum: 25
  XOR         AND         SHL
--------    --------    --------
00001110    00001110
00001011    00001011
--------    --------
00000101    00001010 -> 00010100

00000101    00000101
00010100    00010100
--------    --------
00010001    00000100 -> 00001000

00010001    00010001
00001000    00001000
--------    --------
00011001    00000000

00011001

Da die Register in einem Prozessor nicht beliebig viele binäre Ziffern speichern können, kann es sein, dass bei einer Addition ein Übertrag entsteht, welcher nicht mehr in dem Register Platz findet. Dieses überzählige Bit wird als Carry-Bit bezeichnet. Wenn im folgenden beispielsweise 4-Bit-Register verwendet werden (mit Bit-Nummern 0 bis 3), so bezeichnet das Carry-Bit (abgekürzt mit C) das hypothetische Bit Nummer 4 des Registers. Dasselbe Beispiel wie oben sieht nun folgendermassen aus:



Decimal 14
Decimal 11













Sum: 9 + Carry
C XOR     C AND     C SHL
------    ------    ------
0 1110    0 1110
0 1011    0 1011
------    --------
0 0101    0 1010 -> 1 0100

0 0101    0 0101
1 0100    1 0100
------    --------
1 0001    0 0100 -> 0 1000

1 0001    1 0001
0 1000    0 1000
--------    --------
1 1001    0 0000

1 1001

Das Resultat ist (fälschlicherweise) 9 mit gesetztem Carry-Bit. Nach Ausführung der Addition bleibt das Carry-Bit im Prozessor gesetzt und kann bei Bedarf ausgelesen werden. Somit dient das Carry-Bit als Statusanzeige darüber, ob die vorherige Operation einen Übertrag ergeben hat. In diesem Zusammenhang wird das Bit auch als Flag bezeichnet (Anzeigeflagge).

Ein gesetztes Carry-Flag bedeutet jedoch nur, dass bei der Verknüpfung der Ziffern ein Übertrag ins überzählige Bit stattfand. Je nachdem, wie die Bits interpretiert werden, muss dies jedoch noch lange nicht heissen, dass das Ergebnis deswegen falsch wäre. Bislang wurde in dem Beispiel implizit angenommen, dass die verwendeten Zahlen unsigned, also ohne Vorzeichen zu interpretieren sind. Wenn nun jedoch die Zahlen als signed angenommen werden, so ergibt sich folgende Interpretation der Rechnung:

Decimal: -2
Decimal: -5

Sum: -7 + Carry
  1110
  1011
+ ----
1 1001

Dieses Resultat entspricht nun bereits dem korrekten Ergebnis, wenn die Zahlen im Zweierkomplement codiert sind. Das höchstwertige Bit des 4-Bit-Registers wird als Vorzeichen aufgefasst und als Sign-Bit bezeichnet. Auch dieses Bit bleibt nach Ausführung der Operation als Sign-Flag im Prozessor gespeichert, egal, ob nun die Zahlen als signed oder unsigned zu interpretieren sind. Dadurch ist es sehr einfach möglich, abzufragen, ob das Resultat negativ oder positiv ist (wenn es sich denn um eine signed-Zahl handelt).

Durch die Verwendung des Zweierkomplements können bei der Addition sowohl positive als auch negative Zahlen genau gleich behandelt werden können. Zudem kann durch die Verwendung des Zweierkomplements ganz einfach aus einer Addition eine Subtraktion gemacht werden: Einfach den zweiten Operanden negieren.

Weitere arithmetische Flags

Die oben angesprochenen Flags sind für die eigentliche Operation nicht wichtig, jedoch für nachfolgende Entscheidungen. Beispielsweise soll je nachdem, ob das Sign-Flag gesetzt ist oder nicht (also ob das Resultat positiv oder negativ ist), anderer Code ausgeführt werden. Genauso häufig wird getestet, ob eine Zahl Null ist oder ob eine Zahl grösser oder kleiner als eine andere ist. Solche Entscheidungen werden im Prozessor allesamt mittels der Subtraktion zweier binärer Zahlen getroffen. Nach Ausführung der Subtraktion sind alle benötigten Flags gesetzt und ein Prozessor kann sie auf Wunsch auslesen und entsprechenden Code ausführen.

Für eine korrekte Behandlung und Entscheidung aller möglichen Fälle einer Addition oder Subtraktion sind die oben angesprochenen Carry- und Sign-Flags jedoch noch nicht ausreichend. Zum einen wird das sogenannte Zero-Flag benötigt. Es wird vom Prozessor gesetzt wenn (und nur wenn) das Resultat gleich Null ist. Damit kann beispielsweise geprüft werden, ob zwei Zahlen gleich sind, indem sie voneinander subtrahiert werden. Wenn das Resultat Null ergibt, sind sie gleich gross.

Des weiteren wird das sogenannte Overflow-Flag benötigt, welches angibt, ob aufgrund einer Operation ein Überlauf eines signed-Wertes entstanden ist. Dieses Bit wird gesetzt, wenn bei der Addition zweier Werte der Übertrag an die höchstwertige Stelle sich von dem Carry-Bit unterscheidet.

Für eine Erklärung dieser Verknüpfung wird folgende Überlegung gemacht: Wenn zwei positive signed-Werte addiert werden, ist das Carry-Bit auf jeden Fall gelöscht. Sollte es passieren, dass aufgrund zu grosser Werte ein Übertrag an das höchstwertige Bit entsteht, so wird das Resultat negativ, was nicht korrekt ist. Wenn demgegenüber zwei negative signed-Werte addiert werden, ist das Carry-Bit auf jeden Fall gesetzt, die Summe der beiden Sign-Bits ergibt jedoch für sich alleine betrachtet 0, was nicht korrekt wäre. Das Resultat ist somit nur dann korrekt, wenn zusätzlich ein Übertrag an das höchstwertige Bit entsteht, sprich, die beiden Summanden ausreichend klein sind. Somit ist das Resultat in beiden Fällen genau dann korrekt, wenn der Übertrag an das höchstwertige Bit gleich ist wie das Carry-Bit.

Wenn bei der Addition zwei signed-Summanden mit unterschiedlichem Vorzeichen verwendet werden, so muss unterschieden werden, ob der Betrag des negativen Summanden kleiner oder grösser ist als derjenige des positiven Summanden. Wenn der Betrag des negatven Summanden kleiner ist, so muss das Resultat positiv sein. Aufgrund der Codierung mittels des Zweierkomplements entsteht auf jeden Fall ein Übertrag an das höchstwertige Bit. Da zudem genau einer der Summanden negativ war, wird dadurch auch das Carry-Bit gesetzt werden. Wenn der Betrag des negativen Summanden grösser ist als derjenige des positiven Summanden, so findet aufgrund der Codierung mittels des Zweierkomplements garantiert kein Übertrag an das höchstwertige Bit statt. Da wiederum nur einer der Summanden negativ war, wird jedoch auch das Carry-Bit nicht gesetzt werden. Somit ist das Resultat bei signed-Operanden mit unterschiedlichem Vorzeichen stets korrekt und folgt ebenfalls der Regel, dass das Carry-Flag gleich sein muss wie der Übertrag an das höchstwertige Bit.

Zusammenfassung: Das Carry-, das Overflow-Flag und das Sign-Flag bezeichnen unterschiedliche Dinge. Das Sign-Flag bezeichnet, wie das höchstwertige Bit des Resultates gesetzt ist. Das Carry-Flag bezeichnet, ob der Werteumfang einer unsigned-Zahl gesprengt wurde. Das Overflow-Flag bezeichnet, ob der Werteumfang einer signed-Zahl gesprengt wurde.

Bei der Addition beliebiger unsigned- und signed-Zahlen können somit unterschiedliche Kombinationen aus Carry-, Sign- und Overflow-Flag entstehen. Folgende Tabelle gibt eine Auflistung aller möglichen Fälle. Das Zero-Flag ist hier nicht aufgeführt, es wird einfach dann gesetzt, wenn das Resultat Null ergibt.

Operands           4-Bit Example        unsigned        signed        C S O
---------------------------------------------------------------------------
pos + pos = pos    0011 + 0100 = 0111    3 +  4 =  7    3 +  4 =  7   0 0 0
pos + pos = neg    0100 + 0101 = 1001    4 +  5 =  9    4 +  5 = -7   0 1 1
pos + neg = pos    0101 + 1110 = 0011    5 + 14 =  3    5 + -2 =  3   1 0 0
pos + neg = neg    0100 + 1010 = 1110    4 + 10 = 14    4 + -6 = -2   0 1 0
neg + neg = pos    1011 + 1100 = 0111   11 + 12 =  7   -5 + -4 =  7   1 0 1
neg + neg = neg    1101 + 1110 = 1011   13 + 14 = 11   -3 + -2 = -5   1 1 0

In dieser Tabelle wird ersichtlich, dass das Resultat einer unsigned-Rechnung genau dann falsch ist, wenn das Carry-Flag gesetzt ist. Das Resultat einer signed-Rechnung indes ist genau dann falsch, wenn das Overflow-Flag gesetzt ist. In diesen Fällen ist das Register Unter- oder Überlaufen, was auf englisch als Underflow, beziehungsweise Overflow bezeichnet wird. Hierbei muss jedoch klar getrennt werden zwischen dem Overflow des Registers und dem Overflow-Bit. Von einem Over- oder Under-Flow wird allgemein gesprochen, wenn bei einer beliebigen Operation der Werteumfang eines Typs gesprengt wird und das Resultat somit nicht mit demselben Typ gespeichert werden kann.

Mit was für Tricks diese Flags nun benutzt werden, um zu entscheiden, ob Zahlen grösser, kleiner oder gleich sind, wird auf dieser Seite nicht behandelt. Hierfür wird auf Literatur zu Assembler verwiesen. Die Ausführungen zu den Flags wurden nur der Vollständigkeit halber hier aufgeführt.

Multiplikation

Die Multiplikation funktioniert sehr ähnlich wie die schriftliche Multiplikation im normalen Dezimalsystem:

123 * 456
---------
     1368
     912-
+   456--
---------
    56088

Jede Ziffer des ersten Operanden wird einzeln mit dem ganzen zweiten Operanden multipliziert, wobei das jeweilige Zwischenresultat jeweils um eine Ziffer nach links verschoben (Ziffernverschiebung) und am Schluss aufsummiert wird. Im Computer funktioniert dies nun nach sehr ähnlichen Schema, wobei hier nur jeweils mit 0 oder 1 multipliziert werden muss (was zugegebenermassen nicht allzu schwierig ist). Das Resultat passt in jedem Fall in ein Register, welches doppelt so gross ist wie die ursprünglichen Operanden. Im folgenden Beispiel wird mit 4-Bit Registern gerechnet, womit das Resultat in einem 8-Bit-Register Platz findet.

Decimal: 3
Decimal: 5








Decimal: 15
0011
0101

0011 * 0101
-----------
       0101
      0101-
     0000--
+   0000---
-----------
   00001111

Hier auf dieser Seite sei die Erklärung der Multiplikation mit diesem Beispiel grundsätzlich abgeschlossen. Dennoch sei darauf hingewiesen, dass die tatsächliche Ausführung in einem Prozessor noch stark optimiert werden kann. Da früher Registerplatz sehr rar war, wurde jedes Bit wo möglich doppelt genutzt. Für detailierte Informationen wird jedoch auf andere Seiten verwiesen.

Ein Augenmerk sei jedoch noch darauf gerichtet, dass diese Methode nur dann fehlerfrei funktioniert, wenn beide Operanden positiv sind. Hier ein Beispiel für eine fehlerhafte Berechnung:

Decimal: -3
Decimal: 5








Decimal: 65
1101
0101

1101 * 0101
-----------
       0101
      0000-
     0101--
+   0101---
-----------
   01000001

Die naheliegendste Lösung dieses Problems wäre, die negativen Operanden mittels des Zweierkomplements umzuwandeln, die Multiplikation ganz normal mit positiven Zahlen durchzuführen und am Schluss der Rechnung das Resultat je nach Vorzeichen der beiden Operanden (Minus mal Minus gibt Plus) wieder zu negieren. Die Digitaltechnik kennt auch für die Berechnung von negativen Multiplikationen einige erstaunliche mathematische Tricks. Auf dieser Seite werden sie jedoch nicht weiter erläutert, der interessierte Leser möge sich andernorts schlaumachen.

Division

Bei der Integer-Division wird normalerweise nach einem sogenannten Subtraktions-Algorithmus vorgegangen. Der erste Operand wird Dividend und der zweite Operand Divisor genannt. Die Überlegung beim Subtraktionsalgorithmus ist, dass stets ein möglichst grosses Vielfaches des Divisors vom Dividenden abgezogen wird, bis dass am Schluss der Dividend zu klein wird, um überhaupt noch geteilt zu werden. Dieser Restbetrag wird wie in der Schulbuchmathematik als Rest, oder auf Englisch Remainder genannt.

Um im Binärsystem ein Vielfaches des Divisors herzustellen, können einfach sämtliche Bits nach links verschoben werden. Im folgenden Beispiel werden zwei 8-Bit-Zahlen durch einander dividiert. Der Divisor wird hierbei zu Beginn so weit nach links verschoben, dass die höchstwertige Ziffer 1 der binären Zahl an der höchstwertigen Stelle des 8-Bit-Registers steht. Daraufhin wird sukzessive der Divisor um eine Ziffer nach rechts verschoben (shift right, SHR), während der Dividend immer kleiner wird, wenn der Divisor vollständig im Dividenden Platz findet und von ihm abgezogen wird.

Dividend: 220
Divisor:   12

Dividend: 220
Divisor:  192

Dividend:  28
Divisor:   96

Dividend:  28
Divisor:   48

Dividend:  28
Divisor:   24

Dividend:   4
Divisor:   12

Ergebnis: 18 Rest 4
  11011100
  00001100 -> 11000000 (shifted to the left)

  11011100
- 11000000 1 times
----------
  00011100
- 01100000 0 times
----------
  00011100
- 00110000 0 times
----------
  00011100
- 00011000 1 times
----------
  00000100
- 00001100 0 times
  
10010 remainder 00000100

Auf eine detailierte Beschreibung verschiedener Optimierungen und Randbedingungen wird auf dieser Seite verzichtet. Auch hier wird wieder auf die Spezialbehandlung von negativen Zahlen hingewiesen, Informationen darüber sind jedoch bei anderen Quellen zu finden.

Nächstes Kapitel: ASCII und UTF-8