**Dies ist eine alte Version des Dokuments!**
Zahlensysteme & Datentypen
1. Voraussetzungen
Aus dem Grundlagenfach weisst du bereits, was Zahlensysteme sind. Insbesondere kennst du das Binärsystem und kannst:
- Umrechnen: Dezimalsystem ↔ Binärsystem
- Darstellung negativer Zahlen
- Rechnen mit Binärzahlen: Addition, Multiplikation, Subtraktion
- mithilfe des Binärsystems Mitmenschen (die es verdient haben) eloquent beleidigen können
- Gegeben $x-$Bits für Darstellung → min./max. Zahl bestimmen, die damit dargestellt werden kann
Das Dossier Zahlensysteme aus dem Grundlagenfach findest du hier: Dossier Zahlensysteme (GFIF)
2. Datentypen
Beim Programmieren kennen wir viele verschiedene Datentypen wie:
- Integer (kurz: int): also ganze Zahlen $\ldots,-3,-2,-1,0,1,2,3,\ldots$
- Floats: Gleitkommazahlen, z.B. $3.14$
- Strings: Text, z.B.
"Das EFIF ist super!"
- und viele weitere
Für einen Computer macht es einen grossen Unterschied, ob es sich bei einer Variablen um einen Integer oder einen Float handelt, da diese im Hintergrund ganz anders gespeichert werden. Zum Beispiel ist für ein Computer 4
(int) nicht das Gleiche wie 4.0
(float), auch wenn die beiden Zahlen mathematisch identisch sind.
2.1 Datentypen in Python
In Python muss man sich nicht wirklich um Datentypen kümmern, was sowohl eine der grössten Stärken wie auch Schwächen von Python ist. Zum Beispiel kann man in Python eine Variable ganz einfach direkt festlegen:
i = 42 f = 3.14 s = "Das EFIF ist super!"
Python erkennt dann automatisch, um was für einen Datentypen es sich handelt. Überzeuge dich selbst mit print(type(...))
, dass es sich hier um die folgenden Datentypen handelt:
print(type(i)) # Output: <type 'int'> print(type(f)) # Output: <type 'float'> print(type(s)) # Output: <type 'str'>
In Python kann man eine Variable auch problemlos in einen anderen Typen umwandeln:
i = 42 # zuerst int i = "Ich bin jetzt ein String, holt mich hier raus!" # jetzt ploetzlich String
Aus diesem Grund spricht man in Python von dynamischer Typisierung.
2.2 Statische Typisierung
In vielen anderen Programmiersprachen wie C#, C++ oder Java geniesst man diese Freiheiten nicht. Man spricht dann von statischer Typisierung und typsichere Sprachen. In solchen müssen Variablen explizit deklariert, d.h. es muss angegeben werden, für welchen Datentypen man diese Variable verwenden möchte. Einmal festgelegt, kann der Datentyp nicht mehr verändert werden. Möchte man zum Beispiel einem Integer einen Float zuweisen, so erhält man eine Fehlermeldung:
Zum Beispiel in C++ (was wir für Arduino brauchen werden):
int i; // separate Deklaration ... i = 42; // und Wertzuweisung float f = 3.14; // Deklaration und Wertzuweisung in einer Zeile
Von nun an wollen wir uns nur noch mit typsicheren Programmiersprachen beschäftigen.
3. Ganze Zahlen
Deklariert man eine Integer-Variable int x;
, so wird im Arbeitsspeicher für die Variable x der Speicherplatz reserviert, der einem Integer zusteht. Weise ich der Variable nun einen zu grossen Wert zu, so kann dieser nicht gespeichert werden. In dem Fall muss man auf einen Datentypen ausweichen, dem mehr Speicherplatz zur Verfügung steht.
- Anzahl verschiedene Zahlen
- die kleinste und grösste Zahl
Wir werden bald mit Arduinos, genauer Arduino Unos, arbeiten. Für diese stehen uns drei Datentypen für das speichern von ganzen Zahlen zur Verfügung:
- int: 16 Bit (negative und positive Zahlen)
- unsigned int: 16 Bit (nur positive Zahlen: $0,1,2,\ldots$)
- long: 32 Bit
int t;
die Zeit in Millisekunden speichern.- Für welche Zeitdauer geht dies gut?
- Möchtest du längere Zeiten messen, musst du deinen Code anpassen. Was sind deine Optionen? Reichen diese Änderungen?
4. Gleitkommazahlen
Hilfreiches Online-Tool: https://zahlensysteme-rechner.de
Das Rechnen mit ganzen Zahlen am Computer ist relativ problemfrei. So kann man davon ausgehen, dass der Computer einem immer exakte Resultate liefert - zumindest so lange man nicht den Bereich verlässt, der vom Datentyp (Z.B. int oder long) abgedeckt wird. Zum Beispiel kann man mit einem 32-Bit-Datentyp für ganze Zahlen (z.B. long auf Arduino Uno) sämtliche ganzen Zahlen beschreiben, die es im Bereich $-2147483648$ bis $2147483647$ gibt.
Bei Gleitkommazahlen, also Zahlen mit Nachkommastellen, sieht es da anders aus. Beispielsweise gibt es nur schon im Intervall zwischen $0$ und $1$ unendlich viele rationale und reelle Zahlen! Dies führt dazu, dass Werte meist gerundet werden müssen und damit nicht exakt dargestellt werden können. Rechnet man dann mit diesen gerundeten Werten weiter, so können sich diese Ungenauigkeiten verstärken.
Daraus resultiert folgende Programmierweisheit: Wenn immer möglich, sollte man mit Integers (int/long/uint/…) erledigen. Gleitkommazahlen (float/double) sollte man nur verwenden, wenn es nicht anders geht!
Wissenschaftliche Schreibweise von Dezimalzahlen
Um zu verstehen, wie Gleitkommazahlen in Computern gespeichert werden, lohnt es sich, sich die wissenschaftliche Schreibweise, in Erinnerung zu rufen. Diese Schreibweise solltest du aus dem Mathematikunterricht kennen. In dieser Schreibweise hat eine Zahl immer die folgende Form: $$\pm a \times 10^b$$
- Die Stelle ganz links beinhaltet das Vorzeichen und entscheidet deshalb darüber, ob die Zahl positiv oder negativ ist.
- Die Zahl $a$ vor der Potenz wird Mantisse genannt und erfüllt die Bedingung $1\leq a < 10$. Sie ist also eine Gleitkommazahl, wobei genau eine Ziffer ausser $0$ vor dem Dezimalpunkt steht.
- Die Zahl $b$ wird Exponent genannt. Grosse Zahlen haben einen positiven, kleine einen negativen Exponenten. Du kannst dir vorstellen, dass der Exponent das Komma verschiebt.
Zum Beispiel sieht die wissenschaftliche Schweibweise der Zahl $-72024$ wie folgt aus: $$-7.2024 \cdot 10^4$$ Gut an der wissenschaftlichen Schreibweise ist, dass sie eine Zahl eindeutig beschreibt. Besonders nützlich ist sie für sehr kleine und grosse Zahlen.
- $100023=$
- $0.000000000932=$
- $613453 \cdot 10^{84}=$
Fun fact: Die letzte Zahl entspricht in ihrer Grössenordnung ungefähr der geschätzten Anzahl Atome im sichtbaren Teil des Universums!
Binäre Gleitkommazahlen
Eine Gleitkomma-Dezimalzahl kann man wie folgt darstellen: $$\color{blue}{42}.\color{blue}{13}_\color{magenta}{10}= \color{blue}{4} \cdot \color{magenta}{10}^\color{green}{1} + \color{blue}{2} \cdot \color{magenta}{10}^\color{green}{0} + \color{blue}{1} \cdot \color{magenta}{10}^\color{green}{-1} + \color{blue}{3} \cdot \color{magenta}{10}^\color{green}{-2}$$
Gleichermassen kann eine Gleitkomma-Binärzahl dargestellt werden: $$\color{blue}{101}.\color{blue}{11}_\color{magenta}{2}= \color{blue}{1} \cdot \color{magenta}{2}^\color{green}{2} + \color{blue}{0} \cdot \color{magenta}{2}^\color{green}{1} + \color{blue}{1} \cdot \color{magenta}{2}^\color{green}{0} + \color{blue}{1} \cdot \color{magenta}{2}^\color{green}{-1} + \color{blue}{1} \cdot \color{magenta}{2}^\color{green}{-2} $$
Um eine Binärzahl mit Nachkommastellen im Speicher eines Computers zu speichern, bringt man diese zuerst in die wissenschaftliche Schreibweise. Im Speicher werden dann Vorzeichen, Mantisse und Exponent einzeln gespeichert. Dabei ist ein Bit für das Vorzeichen und jeweils eine feste Anzahl Bits für Mantisse und Exponent festgelegt. Aus dieser Information kann dann die Zahl rekonstruiert werden.
Die wissenschaftliche Schreibweise für Binärzahlen funktioniert analog zu der für Dezimalzahlen. Beachte, dass in der Mantisse die Ziffer vor dem Punkt eine Eins sein muss. Die einzige Ausnahme ist die Zahl $0_2$. Beim Speichern einer Zahl mit Nachkommastellen wird deshalb diese erste $1$ weggelassen - so spart man sich ein Bit! Man sagt, dass die Mantisse normalisiert wird:
In Exponenten möchte man negative Zahlen verhindern. Man addiert zum Exponenten deshalb einen sogenannten Bias. Dieser Bias-behaftete Exponent nimmt dann im Normalfall Werte zwischen $1$ und $2^x-2$ an, wobei $x$ die Anzahl Bits für den Exponenten ist. Damit kann man Exponenten zwischen $-(2^{x-1}-2)$ und $+2^{x-1}-1$ darstellen. Die beiden Sonderfälle, wo der Bias-behaftete Exponent $0$ und $2^x$ ist, werden in der Tabelle erklärt:
Exponent | Mantisse | Beschreibung |
---|---|---|
$E = 0$ | $M = 0$ | Zahl $0$ |
$E = 0$ | $M > 0$ | denormalisierte Zahl (für extrem kleine Zahlen) |
$2^x-1 > E > 0$ | $M \geq 0$ | normalisierte Zahl (Normalfall) |
$E = 2^x-1$ | $M = 0$ | Unendlich |
$E = 2^x-1$ | $M > 0$ | keine Zahl / Not A Number (NAN) |
Diese Zahlendarstellung wird Gleitkommadarstellung genannt, da die Position des Kommas je nach Zahl variiert. Diese Darstellung erlaubt, im Gegensatz zu Darstellungen mit einem festen Platz für das Komma, dass man sowohl sehr grosse wie auch sehr kleine Zahlen mit hoher Genauigkeit speichern kann. Die hier beschriebene Darstellung wird durch die Norm Norm IEEE 754 festgelegt.
Rechne die Binärzahl $101.01_2$ ins Dezimalsystem um.

- Notiere die Zahl zuerst in der wissenschaftlichen Schreibweise für Binärzahlen.
- Bestimme (jeweils als Binärzahl): Vorzeichen, Exponent, Mantisse
- Welche Werte sollen der minimale und maximale Exponent haben können? Bestimme den passenden Bias, den man zum Exponenten hinzuaddiert.
- Normalisiere nun die Mantisse und addiere den Bias zum Exponenten. Notiere: Vorzeichen, Exponent mit Bias, Mantisse.
- Notiere nun die fertige Gleitkommazahl, so wie sie im Computer gespeichert wird.
- Wie sehen die Sonderfälle (siehe Tabelle oben) für eine 16-Bit Gleitkommazahl aus?
- Bestimme die grösste und zweitgrösste Zahl, die man damit darstellen kann als Dezimalzahl. Was fällt dir auf?
- Bestimme die kleinste positive und von Null verschiedene Gleitkommazahl, die man als normalisierte Zahl darstellen kann. Rechne sie in eine Dezimalzahl um.
- Welche Schlüsse ziehst du daraus, wenn es um die Verwendung von ganzen Zahlen und Gleitkommazahlen in der Programmierung geht?
Dezimalzahlen mit Nachkommastellen
Aus dem Grundlagenfach kennen wir den Restwertalgorithmus, mit dem wir ganze Dezimalzahlen (z.B. $132$) ins Binärsystem umrechnen: Wir dividieren immer durch $2$ und fügen dem Rest der Binärzahl hinzu. Wie sieht es aber aus, wenn wir die eine Dezimalzahl mit Nachkommastellen ins Binärsystem umrechnen wollen? Zum Beispiel gilt: $$0.5_{10} = \left(\frac12\right)_{10} = 0.1_2$$ $$0.25_{10} = \left(\frac14\right)_{10} = 0.01_2$$ $$0.125_{10} = \left(\frac18\right)_{10} = 0.001_2$$ Der Algorithmus zur Umrechnung von dezimalen Nachkommastellen ins Binärsystem geht ganz ähnlich wie oben beschrieben, nur multiplizieren wir immer mit $2$. Die Stelle vor dem Komma wird der Binärzahl angehängt, der Rest geht in die nächste Runde bis irgendwann der Rest $0$ erreicht wird. Dann bricht man ab. Viele Zahlen produzieren aber sich wiederholende binäre Nachkommastellen.
Beispiel: $0.375$ ins Binärsystem umwandeln: $$0.375 \cdot 2 = \color{blue}{0}.75$$ $$0.75 \cdot 2 = \color{blue}{1}.5$$ $$0.5 \cdot 2 = \color{blue}{1}.\color{red}{0}$$ Es gilt also: $$0.375_{10} = 0.\color{blue}{011}_2$$
Für Zahlen wie $42.375$ gehen wir wie folgt vor: Wir wandeln den Teil vor und den Teil nach dem Komma separat um und fügen sie dann zusammen:
- $42_{10} = 101010_2$
- $0.375_{10} = 0.011$2
Es gilt also $$42.375_{10} = 101010.011_2$$
- Dezimalbruch $\cfrac{1}{16}$ als Binärzahl
- Dezimalzahl $0.5625$ als Binärzahl
- Dezimalzahl $90.25$ als Binärzahl
- Dezimalzahl $0.42$ als Binärzahl
- Ganze Dezimalzahlen ins Binärsystem umrechnet. Repetition, implementiere selbst den Restwertalgorithmus und verwende nicht z.B.
bin(...)
. - Kleine Dezimalzahlen ($0 < a < 1$, haben also Form $0.<\text{nachkommastellen}>$) wie z.B. $0.375$ ins Binärsystem umrechnet. Achtung: Die Funktion soll ein Parameter
precision
haben, mit der man angeben kann, nach wie vielen binären Nachkommastellen abgebrochen werden soll. - Beliebige Dezimalzahlen, z.B. $42.375$ ins Binärsystem umrechnen kann. Tipp: Rufe darin einfach die beiden Funktionen aus 1. und 2. auf und schon ist man (fast) fertig!
Zusatzaufgaben
int i = 64237 float f = -56736f
$$0011001011001000$$ $$0010101001100111$$
Zusatzaufgaben: Programmieren
- Bestimme die grösste und zweitgrösste Zahl, die man in einem C#-Float (8-Bit Exponent, 23-Bit Mantisse). Verwende Python.
- Wie gross ist die Differenz? Verwende Python.
- Überzeuge dich davon, dass C# nicht zwischen der grössten Zahl und zwischen einer Zahl, die in diesem Gap liegt, unterscheiden kann, zum Beispiel indem du diese von einander subtrahierst. Python hingegen hat da keine Probleme.
Verwende z.B. den folgenden C#-Online-Editor: https://replit.com/languages/csharp
Zusatzaufgaben: Mathematik
Berechne dazu die Potenz, die aus dem MSB (most significant bit, also dem Bit, welches ganz links steht) heraus geht, was $2$ hoch `eine grosse Zahl' ist, was eine sehr grosse Dezimalzahl ergibt. Ein handelsüblicher Taschenrechner ist nicht in der Lage, diese Zahl zu berechnen. Der Trick hier ist, dass du dein Wissen über Logarithmen verwendest. Dann schaffst du diese Rechnung ganz ohne Rechner. Insbesondere benötigst du die Formel für den Basiswechsel $$\log_a k = \frac{\log_b k}{\log_b a} \,.$$