Mandalex

BCD: Binär codierte Dezimalzahlen

Binary Coded Decimals, auf Deutsch "Binär codierte Dezimalzahlen" und kurz BCD sind ein eher unbekannter Typ von Zahlen. Sie sind mir bis jetzt erst auf der Intel-Architektur bekannt. Sie bieten jedoch ein interessantes Feld an Überlegungen:

Die Idee dieser Zahlen beruht auf den mühsamen und zeitaufwändigen Umwandlungen von Dezimalzahlen in Binärformat und umgekehrt. Deshalb hat man bereits seit dem 8088- und allen nachfolgenden 80x86-Prozessoren diese Umwandlungen direkt in den Prozessor hineinimplementiert, und zwar folgendermassen:

Mit Dezimalzahlen kann ein Computer nicht rechnen, sondern nur mit binären Zahlen. BCD-Zahlen sind nun folgendermassen definiert:

ungepackt   gepackt
00000000  = 0000    = 0
00000001  = 0001    = 1
00000010  = 0010    = 2
00000011  = 0011    = 3
00000100  = 0100    = 4
00000101  = 0101    = 5
00000110  = 0110    = 6
00000111  = 0111    = 7
00001000  = 1000    = 8
00001001  = 1001    = 9

Jede ungepackte BCD-Zahl füllt ein ganzes Byte, was bedeutet, dass die Werte 11 bis 255 nicht benutzt werden können. Diese Platzverschwendung ist einer der Hauptnachteile der BCDs. Um dem etwas entgegenzukommen gibt es neben normalen BCDs noch die gepackten BCDs. Diese benötigen nur 4 Bits = ein Nibble. Zwischen ungepackten und gepackten BCDs muss jedoch immer unterschieden werden. Dazu später mehr.

Diese Codierung der Zahlen 0 bis 9 ist schon mal schön und gut. Man könnte jetzt bereits mit ihnen rechnen. so gibt beispielsweise 4 + 5:

4 =     00000100
5 =   + 00000101
        --------
Total:  00001001 = 9

Aber anders herum gibt 6 + 7:

6 =     00000110
7 =   + 00000111
        --------
Total:  00001101 = 13?

Nun, was ist daran falsch? Das Problem ist, dass der Wert 00001101 laut obiger Definition nicht existiert. Wenn nach einer Addition also ein solcher unerlaubter Wert auftritt, muss der Prozessor reagieren. Wenn diese Zahlen normale Binärzahlen wären, so wäre das Resultat 00001101 = 13 richtig. Das korrekte Resultat im BCD-Format jedoch wäre 00000001|00000011 = 1|3 (für zwei Ziffern müssen auch zwei Bytes verwendet werden). Um also von 00001101 auf 00000001|00000011 zu kommen, muss der Prozessor nach jeder Addition überprüfen, ob das Resultat kleinergleich ist wie 9 und, falls dem nicht so ist, die Zahl korrigieren.

Diese Überprüfung macht der Prozessor jedoch nicht automatisch. Wenn beispielsweise im AL-Register die Zahl 00000110 steht und im AH die Zahl 00000111, so wird eine Addition wird mit dem Befehl

 MOV AH, AL

genau zu diesem Problem führen. Der Prozessor hat keine Ahnung, dass er soeben zwei BCD-Zahlen zusammengerechnet hat. Was nun unmittelbar folgen muss, ist der Befehl

 AAA 

Dieser Befehl AAA heisst Ascii Adjust after Addition und führt genau diese Korrektur aus. Warum dieser Befehl so heisst, später. Was dieser Befehl genau macht ist folgendes: Wenn das Resultat einen Wert grösser als 9 enthält, so wird zum Resultat automatisch 246 hinzuaddiert. Dadurch wird automatisch ein Überlauf ins nächste Byte erzeugt, wodurch sich das korrekte Resultat ergibt:

6 =         00000110
7 =       + 00000111
            ----
Total:      00001101 ist grösser als 9, also 246 addieren:
          + 11110110
   00000001|00000011 = 1|3

Um mit BCDs zu rechnen gibt es eine ganze Palette von Befehlen, die extra dafür implementiert wurden. Grundsätzlich sind diese Befehlen nur Korrekturbefehle; das heisst, dass die eigentlichen Rechenoperationen die normalen Befehle SUB, DIV und MUL verwendet werden, jedoch vorher oder nachher eine Korrektur mittels der folgenden wichtigstes Befehle erfolgen muss:

  • AAA: Ascii Adjust after Addition: korrigiert das Resultat nach einer Addition (nur für ungepackte BCDs).
  • AAS: Ascii Adjust after Subtraction: korrigiert das Resultat nach einer Subtraktion (nur für ungepackte BCDs).
  • AAM:Ascii Adjust after Multipliication: korrigiert das Resultat nach einer Multiplikation.
  • AAD: Ascii Adjust before Division: korrigiert den Dividenden vor einer Division.
  • DAA: Das Gleiche wie AAA, jedoch für gepackte BCDs.
  • DAS: Das Gleiche wie AAS, jedoch für gepackte BCDs.

Um die genaue Funktionalität dieser Befehle zu erläutern wird auf Fachliteratur verweisen.

Nun, bisher haben BCDs eigentlich nur Umstände gemacht, sie verbrauchen zuviel Platz, sind mühsam zu rechnen, und man muss sich um die Korrektur auch noch selbst kümmern. Was also bringt das Ganze? BCDs haben verschiedene Daseinsberechtigungen:

BCDs sind dafür da, schnelle Umwandlungen zwischen Dezimalzahlen und Binärzahlen zu ermöglichen. Denn um Dezimalzahlen auf dem Bildschirm ausgeben zu können, müssten einige Umrechnungen stattfinden. Jede Ziffer muss einzeln berechnet werden und zum Ascii-Code der Ziffer '0' hinzugezählt werden. Um diesen Prozess zu beschleunigen, gibt es die BCDs. Aus diesem Grund heissen die dazugehörigen Befehle auch Ascii Adjust.

Ein anderer Grund sind numerische Ungenauigkeiten von Fliesskommazahlen: Da ein Computer grundsätzlich nur mit binären Zahlen rechnen kann, ist es ihm eigentlich unmöglich, Zahlen mit Dezimalpunkt zu speichern. Genau genommen wird beispielsweise die Zahl 0.1 folgendermassen mit 8 Bytes gespeichert:

Bitfolge:           00111101|11001100|11001100|11001101
gespeicherter Wert: 0.1000000014901161

Wie genau Fliesskommazahlen codiert und im Binärformat gespeichert werden, kann in Fachliteratur nachgelesen werden. Man sieht jedoch, dass diese Codierung eine kleine Abweichung vom korrekten Wert aufweist. Da bei normaler Anwendung jedoch kaum mehr als die ersten 6 Nachkommastellen gebraucht werden, kommen diese Fehler kaum zum Vorschein. Fehler können sich jedoch gegenseitig verstärken, und das kann zu üblen Fehlern und Katastrophen in Programmabläufen führen. Als Beispiel betrachtet man die Definition eines Drittels. Dividiert man die Zahl 1 durch 3, so erhält man die Zahl 0.33333333. Dieser Wert ist jeodoch auf einige Stellen nach dem Komma gerundet. Der tatsächlich gespeicherte Wert beträgt etwa 0.3333333432674408. Zieht man nun von 1 diesen Wert Ab, so erhält man zuerst 0.66666666667, nach nochmaligem Abziehen 0.33333333334 und nach einem dritten Abziehen die seltsame Zahl 0.0000000000066667.