Erstellt: 3. Februar 2013 (zuletzt geändert: 27. November 2018)

Wie der Rechner rechnet

Rechnen mit Binärzahlen

Die unterschiedlichen Zahlensysteme und die boolschen Operatoren sollten jetzt ja bekannt sein, aber wie der Computer (in unserem Fall die 6510 CPU) rechnet, wissen wir noch nicht.

Die Addition

Beginnen wir einfach damit, zwei Binärzahlen zu addieren.

 %00010000 <- 16
 %00011010 <- 26
 --------- >> Addition
 %00101010 => 42

Wir addieren also so, wie wir es in der Schule gelernt haben. Nur dass wir hier schon ab %10 (bzw. 2 dezimal) einen Übertrag für die nächste Stelle erhalten.

Der Übertrag, den wir schon vom Dezimal-System aus der Schule kennen:
 17
+18
---
  5 >> 7+8 = 15, also bleibt 5 und ein Übertrag von 1
 35 >> 1+1+1(vom Übertrag) = 3 

Im Binärsystem funktioniert das genau so:
 %0101
+%0111
------
     0 >> %1+%1 = %10, also bleibt %0 und ein Übertrag von %1
    00 >> %1+%0+%1(vom Übertrag) = %10, wieder %0 + Übertrag %1 
   100 >> %1+%1+%1(vom Übertrag) = %11, also %1 + Übertrag %1
  1100 >> %0+%0+%1(vom Übertrag) = %1, also %1 und KEIN Übertag

Kurzer Hardware-Ausflug: Passt das Ergebnis nicht mehr in 8-Bit, so wird im Statusregister des Prozessors das Carry-Flag (Übertrag) gesetzt (= 1), passt das Ergebnis in 8-Bit wird das Carry-Flag gelöscht (= 0). Das Carry-Flag dient also so zu sagen aushilfsweise als neuntes Bit. Mit dem Carry-Flag können wir z. B. 16-Bit Additionen realisieren, die unsere 8-Bit CPU ja nicht beherrscht.

Die Subtraktion

Der Prozessor ist übrigens so dumm, dass er nicht mal „normal“ subtrahieren kann, er addiert stattdessen immer eine negative Zahl. Dazu kehrt er das Vorzeichen der zu subtrahierenden Zahl einfach um und addiert diese dann.
Mit unseren 8-Bit lassen sich bekanntlich Zahlen von 0 bis 255 abbilden. Da die CPU nur Binärzahlen verarbeiten kann, stellt sich nun die Frage, wie man, wenn man nur Nullen und Einsen zur Verfügung hat, ein Vorzeichen abbildet?

Negatives Vorzeichen

Die Lösung ist recht einfach, der Prozessor verwendet das höchste Bit (also Bit-7) zur Unterscheidung, ob es sich um eine positive oder negative Zahl handelt. Da wir dieses Bit aber auch benötigen um Zahlen über +127 darzustellen, ist das Vorzeichen von unserer Interpretation abhängig. Im Programm müssen wir also entscheiden, ob wir die Zahl als vorzeichenbehaftet oder als vorzeichenlos betrachten wollen.
Sorry, aber hier ist ein erneuter Ausflug in die Hardware angebracht:
Um das Erkennen des Vorzeichens zu erleichtern, gibt es im Prozessor ein spezielles Flag (das Negativ-Flag). Dieses Flag wird immer dann gesetzt (= 1), wenn wir mit einer Zahl arbeiten, bei der das höchste Bit (Bit-7) gesetzt ist (die Zahl also negativ ist) und gelöscht (= 0) wenn das Bit nicht gesetzt ist. Vereinfacht gesagt, es enthält den gleichen Wert, wie das höchste Bit der Zahl, mit der wir gerade arbeiten.

Da das höchste Bit für die Vorzeichenerkennung benutzt wird können, wir mit unseren 8-Bit also einen vorzeichenbehafteten Wertebereich von -128 bis +127 abbilden.

Werft doch mal einen Blick auf die folgende Tabelle, um zu sehen, wie diese unterschiedliche Interpretation aussieht.

   Binär   | Hex | ohne Vorz. | mit Vorzeichen
 ----------|-----|------------|---------------
 %00000000 | $00 |      0     |    0
 %00000001 | $01 |      1     |    1
 %00000010 | $02 |      2     |    2
 %00000011 | $03 |      3     |    3
 ...
 %01111100 | $7C |    124     |  124
 %01111101 | $7D |    125     |  125
 %01111110 | $7E |    126     |  126
 %01111111 | $7F |    127     |  127
 %10000000 | $80 |    128     | -128
 %10000001 | $81 |    129     | -127
 %10000010 | $82 |    130     | -126
 %10000011 | $83 |    131     | -125
 ...
 %11111100 | $FC |    252     |   -4
 %11111101 | $FD |    253     |   -3
 %11111110 | $FE |    254     |   -2
 %11111111 | $FF |    255     |   -1
Die Vorzeichenumkehr (Zweierkomplement)

Wie kann man nun die Vorzeichenumkehr durchführen?
Als Beispiel dient mal wieder die gute alte 42 / $2A / %00101010. Versuchen wir nun, das Vorzeichen umzukehren, um auf -42 zu kommen.

Da wir schon wissen, dass das höchste Bit zur Kennzeichnung einer negativen Zahl verwendet wird, könnte man nun meinen, dass es reicht dieses einfach zu setzen.

 %00101010 <- 42
 %10101010 <- ist das -42???
 --------- >> addieren wir zur Gegenprobe beide Zahlen
 %11010100 => -44, eigentlich sollte (42 + -42) 0 ergeben!

Wie wir an der Gegenprüfung, durch Addition der beiden Zahlen sehen, stimmt das nicht, statt 0 kommt -44 raus. Es muss also einen anderen Weg geben.

Die CPU bildet als erstes das sog. Einerkomplement, d. h. wir kehren die Nullen und Einsen einfach um. Dabei wird wieder, wie eben, automatisch das höchste Bit gesetzt, wenn wir von plus nach minus wechseln, bzw. im umgekehrten Fall gelöscht.

 %00101010 <- 42
 %11010101 <- Einerkomplement
 --------- >> Addition
 %11111111 => -1, schon dicht dran, aber immer noch nicht 0

Wir scheinen der Lösung näher zu kommen, aber unsere Gegenprüfung ist immer noch nicht Null. Das Problem wird durch das Zweierkomplement behoben, dabei wird zum Einerkomplement noch eins addiert.

  %00101010 <- 42

  %11010101 <- Einerkomplement
 +%00000001 <- +%1 für Zweierkomplement
 ----------
  %11010110 <- Zweierkomplement
 +%00101010 <- Gegenprüfung durch Addition von +42
 ----------
(1)00000000 => Wir erhalten 0, (den Übertrag können wir ignorieren)

Endlich! Wie erhofft erwartet, kommt bei unserer Gegenprüfung 0 heraus. Den Übertrag können wir hier bei unserer 8-Bit-Addition ignorieren. Darum müssten wir uns kümmern, wenn wir z. B. 16-Bit-Operationen implementieren möchten.

Da ich behauptet habe, die CPU kann nur addieren, muss also auch bei einer negativen Zahl durch das Zweierkomplement das Vorzeichen korrekt umgewandelt werden. Wenn man z. B. 42 – -42 rechnen möchte, kehrt der Prozessor (wie oben erwähnt) das Vorzeichen der zweiten Zahl um und addiert diese dann zur ersten. Es wird also 42+42 gerechnet. Kontrollieren wir mal ob die Umkehr des Vorzeichens auch bei einer negativen Zahl funktioniert.

  %11010110 <- wir beginnen mit -42

  %00101001 <- Einerkomplement
 +%00000001 <- +%1 für Zweierkomplement
 ----------
  %00101010 => und wir erhalten wieder 42

Overflow

Da sind wir also, wir können nun addieren und subtrahieren, haben das Carry- und Negativ-Flag kennengelernt, dann wenden wir unser neues Wissen doch gleich nochmal an.
Rechnen wir noch etwas und gehen davon aus, dass unsere Zahlen auch mal ins Negative reichen. Wie es der Zufall aber so will, haben wir jetzt zwei positive Zahlen, die wir addieren möchten…

 %01000011  ( 67)
+%01000111  ( 71)
-----------------
 %10001010 (-118)  <- ???

Ooops, was ist denn hier passiert?
Durch die Addition wurde das höchste Bit (Bit-7) gesetzt. Es wurde also versehentlich das Kennzeichen für eine negative Zahl gesetzt. Hier kommt meine Aussage von oben zum Tragen, dass wir selbst entscheiden müssen, wie wir die Zahlen interpretieren. Gehen wir hier von vorzeichenbehafteten Zahlen aus, dann ist das Ergebnis offensichtlich falsch. Bei diesen liegt der Wertebereich (wie bereits erwähnt) zwischen -128 und +127. Unser Ergebnis sprengt diesen Bereich, es kommt zum Overflow (Überlauf), ohne auf die Vorzeichen zu achten wäre es korrekt, da wir es dann als 138 werten würden.

Hardware-Alarm: (zum letzten Mal auf dieser Seite, versprochen 😉 )
Um den Overflow zu erkennen, gibt es wieder ein Flag im Statusregister. Ihr kommt bestimmt nie drauf, es ist das OVerflow-Flag. Nun wird es aber nicht einfach immer gesetzt, wenn wir einen Übertrag von Bit-6 nach 7 haben, sondern nur unter den folgenden Bedingungen:

  1. Es findet nur ein Übertrag von Bit-6 nach 7 statt, aber kein gleichzeitiger Übertrag ins Carry-Flag.
    Das trifft auf unser Beispiel von eben mit 67+71 zu!
  2. Es findet kein Übertrag von Bit-6 nach 7 statt, aber einer ins Carry-Flag.
    Dies ist z. B. bei der Addition von zwei negativen Zahlen möglich:

      %10000000  (-128)
    + %11100000  ( -32)
    --------------------
    %(1)01100000 ( +96)

    Hier wurde „versehentlich“ Bit-7 gelöscht und wir erhalten plötzlich eine positive Zahl. Es hat zwar einen Übertrag ins Carry-Flag gegeben, aber von Bit-6 nach 7 gab es keinen, daher wird das Overflow-Flag gesetzt.

Ihr werdet jetzt bestimmt denken, aber ich kann mir doch nicht immer genau überlegen, welche Zahlen wann, wo auftreten können. Doch!
Ihr müsst euch beim Programmieren immer vor Augen halten, was für Werte die jeweilige Rechnung benötigt und was zurückgeliefert wird. Zählt ihr z. B. die Punktzahl des Spielers hoch, dann wird eure Punktzahl wohl nie negativ (ihr zieht max. mal Strafpunkte ab, aber unter Null wird die Punktzahl wohl nie fallen), schreibt ihr aber eine Wirtschaftsimulation, in der das Konto auch mal zeitweise ins Minus fallen kann, dann müsst ihr euch wohl oder übel um die negativen Zahlen kümmern. Davon, dass ein max. Wert von 255 für beide Zwecke viel zu klein ist, will ich erst gar nicht reden.
Einige Probleme lassen sich durch Verwendung von 16 oder gar 32 Bit Zahlen umgehen, aber da wir einen 8-Bit-Prozessor vor uns haben, müssen wir uns darum selbst kümmern und dürfen dabei auch nicht die Geschwindigkeit unseres Programmes vernachlässigen.

Multiplizieren und dividieren

Tja, was soll ich sagen, wer sich den Befehlssatz des 6510 angesehen hat, wird es schon wissen: Der Prozessor kann das überhaupt nicht! Diese Rechenoperationen müssen von euch bei Bedarf selbst implementiert werden oder ihr greift auf vorhandene Routinen (z. B. vom BASIC) zurück. Wie man dies in Assembler realisiert, ist schon ein Wenig anspruchsvoller und wird bei MUL & DIV (Ganzzahl) erklärt. Das solltet ihr euch aber erst anschauen, wenn ihr das gesamte Assembler-Tutorial hinter euch gebracht habt.

Abschließend bleibt noch anzumerken, dass unsere 6510-CPU natürlich auch nicht mit Kommazahlen umgehen kann. Für diese gilt das Selbe, wie für Multiplikation und Division, selbst ist der Mann (bzw. die Frau).
Spätere CPUs haben erst durch mathematische Co-Prozessoren und schließlich durch Integration dieser in die eigentliche CPU viel mehr Möglichkeiten beim Umgang mit Zahlen.

 

So, damit sind die ersten theoretischen Grundlagen geschaffen. Jetzt kann es endlich losgehen.

Mein Hauptthema ist zwar die Assembler-Programmierung, aber wer noch nie mit der Programmierung in Kontakt gekommen ist und direkt etwas Greifbares haben möchte, der kann in einem kleinen BASIC-Kurs erstmal die Bekanntschaft von Variablen, Schleifen, Sprüngen und Co. machen.

Wer direkt mit Assembler loslegen möchte, kann auch einfach zum Assembler-Tutorial springen.

 


Schrott!!Naja...Geht so...Ganz gut...SUPER! (10 Bewertungen | Ø 4,90 von 5 | 98,00%)

Loading...


ZurückWeiter

6 Gedanken zu „Wie der Rechner rechnet“

  1. Warum behauptest Du, dass der 6510 keine Subtraktion kann? Das kann er sehr wohl und dass er es genau als Subtraktion ausführt, sieht man an der Anzahl der Taktzyklen – 1, genauso wie die Addition. Wäre es so, wie Du es suggerierst, bräuchte er 2 Taktzyklen. Speziell, da der 6510 noch nicht einmal einen Zweierkomplement in einem Taktzyklus bilden konnte.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Protected by WP Anti Spam