Zahlensysteme & Datentypen

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)

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.

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.

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.

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.

Aufgabe: Für einen Integer sollen magere $4$ Bits zur Verfügung gestellt werden. Was ist die grösste und kleinste Dezimalzahl, die man damit Darstellen kann?

Lösung

Aufgabe: Für einen Integer sollen nun $n$ Bits zur Verfügung stehen. Finde eine Formel für:
  • Anzahl verschiedene Zahlen
  • die kleinste und grösste Zahl

Lösung

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
Aufgabe: Du möchtest mit einem Arduino eine Stoppuhr bauen und in einer Variablen int t; die Zeit in Millisekunden speichern.
  1. Für welche Zeitdauer geht dies gut?
  2. Möchtest du längere Zeiten messen, musst du deinen Code anpassen. Was sind deine Optionen? Reichen diese Änderungen?

Lösung

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!

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.

Aufgabe: Bringe in die wissenschaftliche Schreibweise:

  1. $100023=$

  2. $0.000000000932=$

  3. $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!

Lösung

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.

Aufgabe:

Rechne die Binärzahl $101.01_2$ ins Dezimalsystem um.

Lösung

Aufgabe: Ziel ist es, die Zahl $101.01_2$ als 16-Bit Gleitkommazahl darzustellen. Dabei sind folgende Anzahl Bits reserviert für:
  1. Notiere die Zahl zuerst in der wissenschaftlichen Schreibweise für Binärzahlen.

  2. Bestimme (jeweils als Binärzahl): Vorzeichen, Exponent, Mantisse

  3. Welche Werte sollen der minimale und maximale Exponent haben können? Bestimme den passenden Bias, den man zum Exponenten hinzuaddiert.

  4. Normalisiere nun die Mantisse und addiere den Bias zum Exponenten. Notiere: Vorzeichen, Exponent mit Bias, Mantisse.

  5. Notiere nun die fertige Gleitkommazahl, so wie sie im Computer gespeichert wird.

  6. Wie sehen die Sonderfälle (siehe Tabelle oben) für eine 16-Bit Gleitkommazahl aus?

Lösung

Aufgabe: Wir betrachten wieder 16-Bit Gleitkommazahlen mit 5-Bit Exponent und 10-Bit Mantisse.

  1. Bestimme die grösste und zweitgrösste Zahl, die man damit darstellen kann als Dezimalzahl. Was fällt dir auf?

  2. Bestimme die kleinste positive und von Null verschiedene Gleitkommazahl, die man als normalisierte Zahl darstellen kann. Rechne sie in eine Dezimalzahl um.

  3. Welche Schlüsse ziehst du daraus, wenn es um die Verwendung von ganzen Zahlen und Gleitkommazahlen in der Programmierung geht?

Lösung

Aufgabe: Für eine Gleitkommazahl stehen insgesamt $n$ Bits zur den Exponenten zur Verfügung. Stelle eine Formel auf oder gib ein Rezept an, wie man den zugehörigen Bias berechnet.

Lösung

Programmieraufgabe: Schreibe eine Funktion (Empfehlung: Python), welcher man eine Binärzahl als String (z.B. „101.01“), sowie die Anzahl Bits für Exponent und Mantisse übergibt. Die Funktion bestimmt dann die passende Mantisse und Exponent (inkl. Bias) für die Zahl und gibt diese zurück. Auch der Spezialfall $0$ soll richtig gehandhabt werden. Die anderen Spezialfälle können ignoriert werden.

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$$

Aufgabe: Wandle wie angegeben um:
  1. Dezimalbruch $\cfrac{1}{16}$ als Binärzahl
  2. Dezimalzahl $0.5625$ als Binärzahl
  3. Dezimalzahl $90.25$ als Binärzahl
  4. Dezimalzahl $0.42$ als Binärzahl

Lösung

Aufgabe: Stelle -25.890625 als binäre 16-Bit float dar

Lösung

Aufgabe: Schreibe eine Funktion z.B. in Python, mit der man …
  1. Ganze Dezimalzahlen ins Binärsystem umrechnet. Repetition, implementiere selbst den Restwertalgorithmus und verwende nicht z.B. bin(...).

  2. 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.

  3. 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!

Lösung

Aufgabe: Warum benötigt man für die Darstellung von 0 eine definierte Ausnahme? Ergeben alle Bits 0 nicht sowieso 0?

Lösung

Aufgabe: Welche Zahl wird hier als Gleitkommazahl (16-Bit mit 5-Bit Exponent und 10-Bit Mantisse) gespeichert: $$1\,10010\,1011010000$$ Bestimme als Binär- und Dezimalzahl.

Lösung

Aufgabe: Warum haben die folgenden beiden Zahlen gemeinsam?
int i = 64237
float f = -56736f

Lösung

Aufgabe: Stelle die Binärzahl 0.00001001001001001 als 16-bit float dar. Probleme? - Stelle das Ergebnis wieder als Binärzahl dar. Was könnte man tun, um das Problem zu vermeiden?

Lösung

Aufgabe: Addiere die beiden folgenden float-Zahlen

$$0011001011001000$$ $$0010101001100111$$

Tipps

Aufgabe:
  1. Bestimme die grösste und zweitgrösste Zahl, die man in einem C#-Float (8-Bit Exponent, 23-Bit Mantisse). Verwende Python.

  2. Wie gross ist die Differenz? Verwende Python.

  3. Ü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

Lösung

Aufgabe: Führe mithilfe von Floats die triviale Berechnung $3.4-3.3$ aus. Was fällt dir auf? Verwende eine typsichere Programmiersprache wie C#. Hier findest du Online-Editoren:


Lösung

Aufgabe: Schätze die Grössenordnung der grössten Zahl, die ein C#-Double (11-Bit Exponent, 52-Bit Mantisse) darstellen kann, ab. Alles was du dazu verwenden darft, ist dein Kopf, also keinen Computer. Taschenrechner, Computer usw. sind tabu!

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} \,.$$

Lösung

Aufgabe: Beweise, dass es im Interval $[0,1]$ überabzählbar unendlich viele reellen Zahlen gibt.
  • ef_informatik/zahlensysteme.1724154364.txt.gz
  • Zuletzt geändert: 2024-08-20 11:46
  • von sca