Einführung in die Programmierung: Fundamentale Datentypen

In der Erstellung von Algorithmen konzentriert man sich zunächst auf die Abfolge der Befehle, die in ihrer Gesamtheit das gegebene Problem (meist die Berechnung von Ausgabewerten aus Eingabewerten) lösen. Dabei gerät man schnell zur Frage, wie dabei auftretende veränderliche Größen modelliert werden. Jede Programmiersprache bietet dazu fundamentale Datentypen an, auf denen gewisse Operationen ausgeführt werden können (wie etwa die ganzen Zahlen zusammen mit den Grundrechenarten). Vorgestellt werden hier fundamentale Datentypen wie ganze Zahlen, Gleitkommazahlen, logische Werte und Zeichen. Zu ihrem besseren Verständnis ist es nötig, in Grundzügen die Speicherorganisation kennenzulernen.

Einordnung des Artikels

Wie? und Was?

Algorithmen und Daten

Der aktuelle Teil des Kurses, also Einführung in die Programmierung, hat bisher vielleicht den Eindruck erweckt, dass es beim Programmieren immer nur darum geht Algorithmen zu schreiben, also Anweisungen (oder Operationen) in geeigneter Reihenfolge anzugeben. Unter einem Programm stellt man sich also einen Vorgang vor und Algorithmierung konzentriert sich auf das Wie?

Der Blick auf die Aufgaben aus dem letzten Abschnitt verrät aber einen weiteren Aspekt: Das Wie? ist untrennbar mit dem Was? verknüpft. Damit ist gemeint, dass die Objekte, mit denen in den Anweisungen jongliert wird, ganz bestimmte Eigenschaften haben, die irgendwo niedergelegt werden müssen.

Dazu betrachte man etwa die Aufgabe:

Ausgabe der Hexadezimalzahlen von (0000)16 bis (FFFF)16, wobei jede Zahl vierstellig angegeben werden soll (also eventuell mit führenden Nullen).

Wie soll man die vierstelligen Hexadezimalzahlen modellieren?

Es soll hier nicht versucht werden, den geeigneten Datentyp für obige Aufgabe zu finden; die Diskussion soll lediglich zeigen, wie selbstverständlich Algorithmen und Datentypen miteinander verschränkt sind; kurz: das Wie? ist untrennbar mit dem Was? verknüpft.

Übersicht über Datentypen

In der Mathematik unterscheidet man grundlegende Mengen wie

Datentypen sind zwar an die Mengen der Mathematik angelehnt, sie müssen aber immer zusammen mit den Operationen gesehen werden, die auf der Menge definiert sind (das Was? ist also mit dem Wie? verknüpft). Kurz:

Ein Datentyp ist eine Wertemenge zusammen mit den darauf definierten Operationen.

Mit den elementaren Datentypen sind immer diejenigen Datentypen einer Programmiersprache gemeint, für die gilt:

  1. Sie sind schon durch die Programmiersprache vorgegeben (müssen also nicht erst vom Programmierer definiert werden).
  2. Sie sind nicht zerlegbar, wie Zahlen oder Zeichen (bei einer Zeichenkette als Datentyp würde man von einem zusammengesetzten Datentyp sprechen).
  3. Gewisse Operationen sind bereits für den Datentyp definiert.

Die zusammengesetzten Datentypen werden oft als Datenstruktur bezeichnet; Beispiele dafür sind komplexe Zahlen, Listen, Vektoren.

In den Bibliotheken von Programmen, die in ein eigenes Programm eingebunden werden können, sind bei allen heutigen Programmiersprachen zahlreiche zusammengesetzte Datentypen enthalten. Und da dort auch eine Vielzahl von Operationen bereits definiert ist, ist es ratsam, zuerst in den Bibliotheken nachzuschlagen, wenn man einen zusammengesetzten Datentyp benötigt.

Erst wenn man eine zu spezielle Datenstruktur benötigt, wird man sie selber definieren (also zusammen mit den entsprechenden Operationen) oder eine vorgegebene Datenstruktur erweitern; man spricht dann von einem selbstdefinierten Datentyp. Die selbstdefinierten Datentypen ermöglichen erst objektorientierte Programmierung (OOP) und werden mit deren Grundlagen ausführlich besprochen.

Aufgabe:

Warum ist es in der Maschinensprache nicht möglich selbstdefinierte Datentypen anzulegen?

Vorerst werden die elementaren Datentypen besprochen, für die es zahlreiche Namen gibt, anstelle von Datentypen sagt man meist nur Typen:

Im Englischen werden sie meist als fundamental types bezeichnet.

Die Liste der elementaren Datentypen von C++ ist sehr lang (und es ist für den Anfänger nicht nötig alle zu kennen) — in Java ist sie deutlich kürzer. Aber da man die Möglichkeit bieten möchte, Programme zu schreiben, die sehr wenig Speicherplatz benötigen und sehr schnell sind, muss der Programmierer für seine Anwendung die optimale Kodierung der Daten auswählen können. Umgekehrt entstehen dadurch Spitzfindigkeiten und Fehlerquellen, die man mit weniger elementaren Datentypen vermieden hätte.

Die elementaren Datentypen können zunächst in zwei Gruppen eingeteilt werden:

  1. Ganzzahlige Typen (integral types)
  2. Gleitkommazahlen (Zahlen mit Nachkommastellen, floating-point types)

Abbildung 1: Übersicht über Datentypen.Abbildung 1: Übersicht über Datentypen.

Bei den ganzzahligen Typen können wiederum drei Untergruppen unterschieden werden:

Die ersten beiden Untergruppen mögen verwundern: warum sind sie in ganzzahligen Typen enthalten? Rechner-intern werden sie wie ganze Zahlen behandelt und bilden daher keine eigene Gruppe.

Abbildung 2: Übersicht über die elementaren Datentypen in C++.Abbildung 2: Übersicht über die elementaren Datentypen in C++.

Abbildung 3: Im Unterschied die (schlankere) Übersicht über die elementaren Datentypen in Java.Abbildung 3: Im Unterschied die (schlankere) Übersicht über die elementaren Datentypen in Java.

Um die Unterscheidung zwischen den Datentypen und die mit ihnen erlaubten Operationen zu verstehen, ist es unerlässlich zu kennen, wie der Speicher organisiert ist, in dem eine Variable abgelegt wird (Größe des Speichers, Interpretation des Inhaltes).

Speicherorganisation und elementare Datentypen

Mögliche Darstellungen für ganze Zahlen

Zunächst mag es merkwürdig erscheinen, dass die meisten elementaren Datentypen mit oder ohne Vorzeichen angeboten werden (etwa int und unsigned int). Dies wird aber verständlicher, wenn man sich überlegt, wie man ganze Zahlen abspeichern kann.

Für positive Zahlen sollte klar sein: sie werden im Speicher als Binärzahlen dargestellt — man spricht von der Binärdarstellung.

Aber wie werden negative Zahlen dargestellt? Naheliegend ist es ein Bit für das Vorzeichen zu reservieren und den Betrag der Zahl in den verbleibenden Bits darzustellen. Man nennt dies die Vorzeichendarstellung. Daneben gibt es auch die Zweierkomplementdarstellung, die komplizierter ist, aber leichter zu verarbeiten ist als die Vorzeichendarstellung.

Im Folgenden werden alle Darstellungen mit 4 Bit Speicherplatz diskutiert; wie man diese Überlegungen auf größere Speicherzellen überträgt, sollte dann nicht schwer sein.

Binärdarstellung

Hat man 4 Bit zur Verfügung kann man insgesamt 24 = 16 verschiedenen Zahlen bilden; beginnt man bei 0, ist die größte darzustellende Zahl 15 (für die Zahl 16 benötigt man schon ein Bit mehr):

0000        0
0001        1
0010        2
0011        3
0100        4
0101        5
0110        6
0111        7
1000        8
1001        9
1010        10
1011        11
1100        12
1101        13
1110        14
1111        15

Entsprechend erhält man bei 8 Bits = 1 Byte:

00000000        0            01000000       64             10000000     128          11000000       192
00000001        1            01000001       65             10000001     129          11000001       193
00000010        2            01000010       66             10000010     130          11000010       194
00000011        3            01000011       67             10000011     131          11000011       195
00000100        4            01000100       68             10000100     132          11000100       196
00000101        5            01000101       69             10000101     133          11000101       197
00000110        6            01000110       70             10000110     134          11000110       198
00000111        7            01000111       71             10000111     135          11000111       199
00001000        8            01001000       72             10001000     136          11001000       200
00001001        9            01001001       73             10001001     137          11001001       201
00001010        10           01001010       74             10001010     138          11001010       202
00001011        11           01001011       75             10001011     139          11001011       203
00001100        12           01001100       76             10001100     140          11001100       204
00001101        13           01001101       77             10001101     141          11001101       205
00001110        14           01001110       78             10001110     142          11001110       206
00001111        15           01001111       79             10001111     143          11001111       207
00010000        16           01010000       80             10010000     144          11010000       208
00010001        17           01010001       81             10010001     145          11010001       209
00010010        18           01010010       82             10010010     146          11010010       210
00010011        19           01010011       83             10010011     147          11010011       211
00010100        20           01010100       84             10010100     148          11010100       212
00010101        21           01010101       85             10010101     149          11010101       213
00010110        22           01010110       86             10010110     150          11010110       214
00010111        23           01010111       87             10010111     151          11010111       215
00011000        24           01011000       88             10011000     152          11011000       216
00011001        25           01011001       89             10011001     153          11011001       217
00011010        26           01011010       90             10011010     154          11011010       218
00011011        27           01011011       91             10011011     155          11011011       219
00011100        28           01011100       92             10011100     156          11011100       220
00011101        29           01011101       93             10011101     157          11011101       221
00011110        30           01011110       94             10011110     158          11011110       222
00011111        31           01011111       95             10011111     159          11011111       223
00100000        32           01100000       96             10100000     160          11100000       224
00100001        33           01100001       97             10100001     161          11100001       225
00100010        34           01100010       98             10100010     162          11100010       226
00100011        35           01100011       99             10100011     163          11100011       227
00100100        36           01100100       100            10100100     164          11100100       228
00100101        37           01100101       101            10100101     165          11100101       229
00100110        38           01100110       102            10100110     166          11100110       230
00100111        39           01100111       103            10100111     167          11100111       231
00101000        40           01101000       104            10101000     168          11101000       232
00101001        41           01101001       105            10101001     169          11101001       233
00101010        42           01101010       106            10101010     170          11101010       234
00101011        43           01101011       107            10101011     171          11101011       235
00101100        44           01101100       108            10101100     172          11101100       236
00101101        45           01101101       109            10101101     173          11101101       237
00101110        46           01101110       110            10101110     174          11101110       238
00101111        47           01101111       111            10101111     175          11101111       239
00110000        48           01110000       112            10110000     176          11110000       240
00110001        49           01110001       113            10110001     177          11110001       241
00110010        50           01110010       114            10110010     178          11110010       242
00110011        51           01110011       115            10110011     179          11110011       243
00110100        52           01110100       116            10110100     180          11110100       244
00110101        53           01110101       117            10110101     181          11110101       245
00110110        54           01110110       118            10110110     182          11110110       246
00110111        55           01110111       119            10110111     183          11110111       247
00111000        56           01111000       120            10111000     184          11111000       248
00111001        57           01111001       121            10111001     185          11111001       249
00111010        58           01111010       122            10111010     186          11111010       250
00111011        59           01111011       123            10111011     187          11111011       251
00111100        60           01111100       124            10111100     188          11111100       252
00111101        61           01111101       125            10111101     189          11111101       253
00111110        62           01111110       126            10111110     190          11111110       254
00111111        63           01111111       127            10111111     191          11111111       255

Vorzeichendarstellung

Die Vorzeichendarstellung erhält man sofort aus der Binärdarstellung, indem man das erste Bit als Vorzeichen liest, die restlichen drei Bits stehen für den Betrag der Zahl. Zum Beispiel verwendet man 0 für + und 1 für -.

0000        0
0001        1
0010        2
0011        3
0100        4
0101        5
0110        6
0111        7
1000        0
1001        -1
1010        -2
1011        -3
1100        -4
1101        -5
1110        -6
1111        -7

Die Vorzeichendarstellung hat zwei Nachteile:

Aufgabe:

Es sollen zwei Zahlen addiert werden, so dass das Ergebnis immer noch im gegebenen Zahlenbereich (bei 4 Bit) liegt. Zum Beispiel 7 + 5 in der Binärdarstellung oder 2 + 3 in der Vorzeichendarstellung.

Formulieren Sie: Für welche Zahlen erhält man das richtige Ergebnis, wenn die Addition als Addition der kodierten Zahlen (mit Basis 2) durchgeführt wird.

Lösung:

In der Binärdarstellung kommt immer das richtige Ergebnis heraus, denn die Zahlen sind richtig angeordnet und es darf keinen Unterschied machen, ob man zuerst ins Dualsystem umrechnet und dann addiert oder ob man die Dezimalzahlen addiert und dann ins Dualsystem umrechnet.

Bei der Vorzeichendarstellung ist die Addition komplizierter:

1. Addiert man zwei positive Zahlen, ist das Ergebnis richtig (Begründung wie bei der Binärdarstellung).

2. Addiert man eine positive und eine negative Zahl, erhält man zwei unterschiedliche Ergebnisse. Zum Beispiel 2 + (-3) = -1. Übersetzt man beide Zahlen in die Binärdarstellung, erhält man:

0010 + 1011

Addiert man diese Zahlen, so wie man Dualzahlen addieren muss, erhält man:

0010 + 1011 = 1101 = -5

In der Binärdarstellung ist 1101 eine negative Zahl vom Betrag 5, also als -5 zu interpretieren.

3. Addiert man zwei negative Zahlen, zum Beispiel (-2) + (-3), so ergibt sich:

1010 + 1011 = 1101 = -5

Hier stimmt das Ergebnis; man kann allgemein zeigen, dass in der Binärdarstellung die Addition immer dann mit den Dualzahlen ausgeführt werden darf, wenn beide Summanden positiv oder negativ sind; ist einer positiv und einer negativ, führt die Addition der Dualzahlen zum falschen Ergebnis.

Eine Addition, die den gegebenen Zahlenbereich verlässt, muss in beiden Fällen gesondert behandelt werden.

Die oben genannten Nachteile der Vorzeichendarstellung behebt die sogenannte Zweierkomplementdarstellung.

Zweierkomplementdarstellung

Die letzte Aufgabe liefert den Hinweis, wie man negative ganze Zahlen kodiert: Man sucht eine Anordnung mit folgenden Eigenschaften:

Die folgende Anordnung der Zahlen im Kreis erfüllt beide Bedingungen.

Abbildung 4: Zweierkomplementdarstellung als Kreis.Abbildung 4: Zweierkomplementdarstellung als Kreis.

Beispiel: Addition von Zahlen in Zweierkomplementdarstellung

Man kann an den Beispielen der Aufgabe oben testen, ob die Addition stets mit Dualzahlen ausgeführt werden darf:

1. Bei primitiven Zahlen ist es klar (solange der Zahlenbereich nicht verlassen wird).

2. Addiert man zum Beispiel 2 + (-3) = -1, so erhält man jetzt mit Dualzahlen:

0010 + 1101 = 1111 = -1

3. Addiert man zwei negative Zahlen, zum Beispiel (-2) + (-3), so ergibt sich:

1110 + 1101 = 1011 = -5

Dabei hätte sich allerdings ein Übertrag ergeben, er wurde weggelassen.

Die Zweierkomplementdarstellung hat also die Vorteile, dass man am ersten Bit das Vorzeichen einer Zahl erkennt und dass man immer ihre Dualzahlen addieren darf. Man könnte einwenden, dass ein Vorzeichenwechsel nicht so einfach ist wie in der Vorzeichendarstellung — dort musste nur das erste Bit invertiert werden. Aber auch hier ist der Vorzeichenwechsel einfach:

Zum Vorzeichenwechsel bildet man das Komplement (alle Bits invertieren) und addiert anschließend 1.

Aufgabe:

Überprüfen Sie an Abbildung 4, dass diese Operation tatsächlich den Vorzeichenwechsel liefert.

Die Subtraktion zweier Zahlen in Zweierkomplementdarstellung wird dann aus dem Vorzeichenwechsel und der anschließenden Addition zusammengesetzt.

Nach diesen ausführlichen Überlegungen zur Zweierkomplementdarstellung sollte klar geworden sein, warum man verschiedene elementare Datentypen anbietet und warum man diese penibel unterscheiden muss. In der Mathematik konnte man die verschiedenen Zahlenbereiche problemlos ineinander einbetten (natürliche Zahlen, ganze Zahlen, rationale Zahlen, reelle Zahlen). Für den Computer, der den Inhalt einer 4 Bit-Speicherzelle interpretieren muss, ist das nicht so einfach: der Eintrag

1111

steht in der

Nach diesen Ausführungen, sollte es nicht mehr schwer sein, die zahlreichen elementaren Datentypen von C++ oder Java zu verstehen.

Boolesche Variable

Boolesche Variable oder Wahrheitswerte können nur zwei Werte annehmen: true und false, die rechnerintern durch 1 und 0 dargestellt werden; eine Boolesche Variable kommt daher mit einem Bit aus.

Verwendet werden Boolesche Variable bei Bedingungsprüfungen, Alternativen und zum Steuern von Schleifen (soll eine Schleife abbrechen oder nochmals durchlaufen werden).

Zeichen

Mit einem Zeichen ist immer ein einziges Zeichen gemeint, also eine Ziffer, ein Buchstabe, ein Sonderzeichen oder auch nicht-sichtbare Zeichen wie Steuerzeichen (Zeilenvorschub, Tabulator).

Unterscheiden muss man Zeichen von Zeichenketten (strings), die keine elementaren Datentypen mehr sind und intern aus Zeichen aufgebaut werden.

Ausgangspunkt in jeder Programmiersprache für den Datentyp Zeichen ist der ASCII-Code (ASCII = American Standard Code for Information Interchange). Er umfasst 27 = 128 Zeichen, die von 0 bis 127 durchnumeriert werden.

Der ASCII-Code stammt aus der Anfangszeit des Computers als jedes Bit Speicherplatz wertvoll war; für heutige Ansprüche reicht er nicht mehr, da man auch Sonderzeichen — und diese für zahlreiche Sprachen — darstellen möchte. Heute werden stattdessen zur Kodierung von Texten Erweiterungen des ASCII-Codes benutzt (wie Unicode, UTF-8, UTF-16 und so weiter).

Die Zeichen mit der Kodierung von 0 bis 32 sind nicht-sichtbare Steuerzeichen, ebenso 127; die sichtbaren Zeichen des ASCII-Codes zeigt folgende Tabelle:

33              !
34              "
35              #
36              $
37              %
38              &
39              '
40              (
41              )
42              *
43              +
44              ,
45              -
46              .
47              /
48              0
49              1
50              2
51              3
52              4
53              5
54              6
55              7
56              8
57              9
58              :
59              ;
60              <
61              =
62              >
63              ?
64              @
65              A
66              B
67              C
68              D
69              E
70              F
71              G
72              H
73              I
74              J
75              K
76              L
77              M
78              N
79              O
80              P
81              Q
82              R
83              S
84              T
85              U
86              V
87              W
88              X
89              Y
90              Z
91              [
92              \
93              ]
94              ^
95              _
96              `
97              a
98              b
99              c
100             d
101             e
102             f
103             g
104             h
105             i
106             j
107             k
108             l
109             m
110             n
111             o
112             p
113             q
114             r
115             s
116             t
117             u
118             v
119             w
120             x
121             y
122             z
123             {
124             |
125             }
126             ~

Gleitkommazahlen

Für Zahlen mit Nachkommastellen — hier werden sie als Gleitkommazahlen bezeichnet — verwendet man in der Mathematik oder in den Naturwissenschaften keine strengen Regeln, wie sie dargestellt werden müssen. Es gibt nur einige lockere Konventionen, die sich an die Regeln anlehnen, die Taschenrechner üblicherweise anwenden:

Im Computer werden Gleitkommazahlen normiert dargestellt — intern wieder mit Dualzahlen; damit ist gemeint, dass jede Zahl aus vier Bestandteilen besteht:

  1. Vorzeichen
  2. Mantisse (eine Zahl zwischen 0 und 1)
  3. Vorzeichen des Exponenten
  4. Betrag des Exponenten

Bildet man aus obigen Dezimalzahlen die normierte Darstellung, so lauten etwa:

Wie oben gezeigt wurde, gibt es für Gleitkommazahlen in jeder Programmiersprache unterschiedliche Datentypen (etwa float und double in C++). Diese unterscheiden sich darin, wieviele Bits für Mantisse und Exponent vorgesehen sind; das Vorzeichen erhält immer ein Bit.

Damit ist aber auch klar, dass die Datentypen Zahlen

Aufgabe:

Die folgende Abbildung zeigt eine Möglichkeit, wie sich Vorzeichen, Mantisse und Exponent den Speicherplatz eines Bytes teilen können.

Abbildung 5: 8-Bit-Speicherzelle für die normierte Darstellung einer Gleitkommazahl.Abbildung 5: 8-Bit-Speicherzelle für die normierte Darstellung einer Gleitkommazahl.

Welche Zahlen können dargestellt werden, wenn obige normierte Darstellung vorausgesetzt wird?

Die Darstellung lässt sich sogar noch verbessern: