Zahlensysteme & Datentypen
Lernziele
Grundsätzlich kann alles bis und mit Kapitel „Weitere Aufgaben“ geprüft werden. Die folgenden Lernziele dienen als eine Art Checklist, sind aber nicht zwingend komplett:
Wissen, warum Datentypen wichtig sind in C Sprachen.
Dynamische und statische Typisierung vergleichen. Vor- und Nachteile?
Zahlen in wissenschaftlicher Schreibweise darstellen.
Umwandlung von ganzen Zahlen (ints) dezimal ↔ binär
Binärzahl in Gleitkommadarstellung bringen …
… und zugehörige Fachbegriffe (Mantisse, …) erklären können.
Umwandlung von Gleitkommazahlen dezimal ↔ binär
Umwandlung Gleitkommadarstellung in Binärzahl oder Dezimalzahl
Sonderfälle für binäre Gleitkommazahlen kennen und verstehen.
Binäre floats addieren können.
Codes zu den obigen Punkten verfassen können in Python oder C#.
Praktische Python Befehle
# Length
len("laenge von einem String oder Liste ...")
# Data types
str(42)
int("132")
float(13)
type(42)
bin(132)
# Strings
s = "ich bin ein String"
s[2:]
s[4:7]
s[:5]
s.replace('b','B')
li = s.split(' ')
s.strip(" hallo ")
s.lstrip(" hallo ")
s.rstrip(" hallo ")
s = s.upper()
s = s.lower()
s.find("bin")
many_zeros_string = "0" * 100
## TIPP: In diesem Thema bietet es sich meist an, mit Strings anstelle Listen zu arbeiten. Sollte man aber das Bedürfnis nach Listen haben, hier die wichtigsten Befehle:
li = ['k','s','r']
len(li)
li.append('romanshorn')
li.pop(2)
li.remove('k')
print(li)
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.
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
Insgesamt können $2^4 = 16$ verschiedene Zahlen damit dargestellt werden. Der Datentyp int
beinhaltet sowohl positive als auch negative Zahlen:
kleinste Zahl: $-8$
grösste Zahl: $7$
Möchte man nur Zahlen $\geq 0$ haben, so spricht man von einem unsigned int (uint). Bei $4$ Bits ist die grösste Zahl dann also $15$.
Aufgabe: Für einen Integer sollen nun $n$ Bits zur Verfügung stehen. Finde eine Formel für:
Lösung
Anzahl: $2^n$
int:
uint:
kleinste Zahl: $0$
grösste Zahl: $2^{n}-1$
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.
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?
Lösung
Nur für knapp 33 Sekunden!
Datentyp ändern in:
unsigned int: Reicht für $2^{16}-1\,\text{ms}$, also knapp $66$ Sekunden. Achtung: $-1$ weil kleinste Zahl $0$ ist für unsigned int.
long: Reicht für knapp $25$ Tage. D.h. dies kann ein Problem werden für Projekte, die länger laufen sollen (z.B. eine Uhr)
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.
Aufgabe: Bringe in die wissenschaftliche Schreibweise:
$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!
Lösung
$100023 = 1.00023 \cdot 10^5$
$0.000000000932 = 9.32 \cdot 10^{-10}$
$613453 \cdot 10^{84} = 6.13453 \cdot 10^{89}$
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.
Aufgabe:
Rechne die Binärzahl $101.01_2$ ins Dezimalsystem um.
Lösung
$$101.01_2 = 1 \cdot 2^{2}+ 0 \cdot 2^{1}+ 1 \cdot 2^{0}+ 0 \cdot 2^{-1}+ 1 \cdot 2^{-2}= 5.25_{10}$$
Aufgabe:
Ziel ist es, die Zahl $101.01_2$ als
16-Bit Gleitkommazahl darzustellen. Dabei sind folgende Anzahl Bits reserviert für:

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?
Lösung
$$101.01_2 = \color{red}{1.0101}_2 \cdot 2^{\color{green}{2}_{10}} = \color{red}{1.0101}_2 \cdot 2^{\color{green}{10}_2}$$
Vorzeichen: $+$, also $0_2$
Exponent: $10_2$
Mantisse: $1.0101_2$
Da $5$ Bit für Exponenten: Mit 5 Bit gibt $2^5 = 32$ Möglichkeiten, tiefster und höchster Exponent sind reserviert für Sonderfälle (siehe Tabelle), es bleiben also $2^5 - 2 = 30$ Möglichkeiten. Regulärer Exponent geht damit von $-14 \leftrightarrow +15$. Bias muss also $+15 = 01111_2$ sein und nicht $14_{10}$, da der Bias-behaftete Exponent der kleinsten normalen Zahl $00001_2$ ist und nicht $00000_2$, da dieser Exponent die Zahl $0$ (oder eine denormalisierte Zahl) darstellt.
minimaler Exponent: $-(2^{5-1}-2) = -14$
maximaler Exponent: $2^{5-1}-1 = 15$
Bias: $B = 15_{10} = 01111_2$
Vorzeichen: $0_2$
Exponent mit Bias: $10_2 + 01111_2 = 10001_2$
Mantisse: $0101000000_2$
$$0\,10001\,0101000000$$
Tabelle, wobei $x=5$ (da $5$ Bits für Exponenten):
Exponent | Mantisse | Beschreibung |
$E = 0$ | $M = 0$ | Zahl $0$ |
$E = 0$ | $M > 0$ | denormalisierte Zahl (für extrem kleine Zahlen) |
$31 > E > 0$ d.h. $30 \geq E \geq 1$ | $M \geq 0$ | normalisierte Zahl (Normalfall) |
$E = 31$ | $M = 0$ | Unendlich |
$E = 31$ | $M > 0$ | keine Zahl / Not A Number (NAN) |
Aufgabe:
Wir betrachten wieder 16-Bit Gleitkommazahlen mit 5-Bit Exponent und 10-Bit Mantisse.
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?
Lösung
grösste Zahl: $$0\,11110\,1111111111 = 1.1111111111_2 \cdot 2^{15_{10}} = 2^{15} + 2^{14} + \ldots + 2^6 + 2^5 = 65504$$
zweitgrösste Zahl: $$0\,11110\,1111111110 = 1.1111111110_2 \cdot 2^{15_{10}} = 2^{15} + 2^{14} + \ldots + 2^6 + 0 \times 2^5 = 65472$$
Beachte: Es gibt zwischen diesen Zahlen einen beachtlichen Bereich, der NICHT dargestellt werden kann als $16-$Bit Zahl, zum Beispiel die Zahl $65500$
Kleinste positive normalisierte Zahl: $$0\,00001\,0000000000 = 1.0000000000_2 \cdot 2^{-14_{10}} = 2^{-14} \approx 6.103 \cdot 10^{-5}$$
Zweitkleinste positive normalisierte Zahl: $$0\,00001\,0000000001 = 1.0000000001_2 \cdot 2^{-14_{10}} = 2^{-14} + 2^{-24} \approx 6.109 \cdot 10^{-5}$$
Gute Faustregel: Wenn man etwas mit ganzen Zahlen umsetzen kann, sollte man dies tun. Beispiel: Code für Stoppuhr. Die Zeit könnte man in Sekunden in Gleitkommazahlen speichern. Wahrscheinlich ist es aber besser, die Zeit in Millisekunden in ganzen Zahlen zu speichern. Auf Arduinos sollte man, wenn möglich, Gleitkommazahlen (floats) komplett vermeiden, da diese nicht nativ unterstützt werden.
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
$$B = 2^{n-1}-1 = 011\ldots1_2$$
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.
Lösung
def binary_string_to_float(bin_str,len_exp=5,len_mant=10):
# SIGN
sign = '0'
if bin_str[0] == '-': sign = '1'
# SPECIAL CASE: ZERO
if '1' not in bin_str: return [sign,'0'*len_exp,'0'*len_mant]
# TODO: other special cases (too small, too big, nan)
# if int convert into float
if not '.' in bin_str: bin_str += '.'
# EXPONENT, MANTISSA
bin_str = bin_str.lstrip('-').lstrip('0') # remove unnecessary stuff
bias = (2**len_exp - 2)//2
mantissa = bin_str
# determine exponent
i_one = bin_str.find('1') # index of first '1' from left
i_point = bin_str.find('.') # index of decimal point
exponent = i_point - i_one
if exponent > 0: exponent -= 1
# remove point from mantissa
if '.' in bin_str: mantissa = bin_str[0:i_point] + bin_str[i_point+1::]
# add bias to exponent
exp_w_bias = bin(exponent+bias)[2:]
exp_w_bias = '0' * (len_exp - len(exp_w_bias)) + exp_w_bias
if len(exp_w_bias) > len_exp:
return [sign,'1'*len_exp,'0'*len_mant]
# normalize mantissa
mantissa = mantissa.lstrip('0')
mantissa_normalized = mantissa[1::] # remove first '1' (i.e. normalize)
# ensure mantissa has correct length
if len(mantissa_normalized) > len_mant:
mantissa_normalized = mantissa_normalized[0:len_mant]
else:
mantissa_normalized += '0' * (len_mant - len(mantissa_normalized))
# TODO: round mantissa correctly
return [sign,exp_w_bias,mantissa_normalized]
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$$
Aufgabe: Wandle wie angegeben um:
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
Lösung
$0.0001_2$ weil $\cfrac{1}{16} = 2^{-4}$
$0.1001_2$
$1011010.01_2$
$.0110101110\ldots_2$ (geht noch weiter, nach 10 NKS abgebrochen)
Aufgabe: Stelle -25.890625 als binäre 16-Bit float dar
Lösung
Vorzeichen negativ, also $0$
Vorkommastellen: $25_{10} = 11001_2$
Nachkommastellen: $0.890625_{10} = 0.111001_2$
ganze Binärzahl: $11001.111001_2$
wissenschaftliche Schreibweise: $1.1001111001_2 \cdot 2^4 $
Exponent:
Exponent ohne Bias: $4_{10} = 00100_2$
Bias ist: $15_{10} = 01111_2$
Exponent mit Bias: $19_{10} = 10011_2$
Mantisse:
Zahl als float also: $$1100111001111001_2$$
Programmieraufgabe: Schreibe eine Funktion z.B. in Python, mit der man …
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!
Lösung
Weitere Aufgaben
Aufgabe: Warum benötigt man für die Darstellung von 0 eine definierte Ausnahme? Ergeben alle Bits 0 nicht sowieso 0?
Lösung
Umrechnung ohne die festgelegten Spezialfälle:
Vorzeichen: $0$, also positiv
Exponent:
Mantisse:
Zahl als Binärzahl also: $$+1.0000000000_2 \cdot 2^{00000_2}$$
das ist zwar klein, aber immer noch viel zu gross…
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
Vorzeichen: $1$, also negativ
Exponent:
Mantisse:
Zahl als Binärzahl also: $$-1101.101_2$$
Zahl als Dezimalzahl:$$-1\cdot 2^3 + 1\cdot 2^2 + 0\cdot 2^1 + 1\cdot 2^0 + 1\cdot 2^{-1} + 0\cdot 2^{-2} + 1\cdot 2^{-3} = -13.625$$
Aufgabe: Warum haben die folgenden beiden Zahlen gemeinsam?
int i = 64237
float f = -56736f
Lösung
Beide haben die gleiche Binärrepräsentation
$$1111 1010 1110 1101$$
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
wissenschaftliche Schreibweise: $1.001001001001_2 \cdot 2^{-5} $
Exponent:
Exponent ohne Bias: $-5_{10} = 11011_2$
Bias ist: $15_{10} = 01111_2$
Exponent mit Bias: $19_{10} = 01010_2$
Mantisse:
Zahl als float also: $$001010001001001001_2$$
oder doch nicht?!? Und jetzt?
Aufgabe: Addiere die beiden folgenden float-Zahlen
$$0011001011001000$$
$$0010101001100111$$
Tipps
Exponenten ermitteln
Mantissen ermitteln
Exponenten angleichen, Komma der Mantissen entsprechend verschieben. Bias beibehalten.
Addition durchführen
Komma verschieben, Exponent angleichen
Runden
Zusammensetzen
Lösung
Exponent 1: $2^{12}$
Exponent 2: $2^{10}$
Mantisse 1: $1.1011001_2$
Mantisse 2a: $1.1001100111_2$
angeglichene Mantisse 2b: $0.011001100111_2$
Addition Mantisse 1 und 2b: $10.000110000111_2$
wissenschaftliche Schreibweise: $1.0000110000111_2 \cdot 2^{13}$
Runden: $1.0000110000111_2$
Die erste Ziffer, die weggeschnitten wird, ist eine 1, und danach gibt es weitere 1. Somit aufrunden.
aufgerundet: $1.0000110001_2 \cdot 2^{13}$
als float: $$0011010000110001$$
Zusatzaufgaben: Programmieren
Aufgabe:
Bestimme die grösste und zweitgrösste Zahl, die man in einem C#-Float (8-Bit Exponent, 23-Bit Mantisse) speichern kann. 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
Lösung
Grösste Zahl: $340282346638528859811704183484516925440 \approx 3.4 \cdot 10^{38}$
Zweitgrösste Zahl: $340282326356119256160033759537265639424$
Differenz: $20282409603651670423947251286016 \approx 2 \cdot 10^{31}$, also eine riesige Differenz
Man erhält immer $0$, z.B. ergibt folgende Subtraktion $0$: $$340282346638528859811704183484516925440\text{f} - 340282340000000000000000000000000000000\text{f}$$
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
Resultat ist $0.1000001$. Also nicht exakt, Grund: Rundungsfehler. $3.3$ und $3.4$ haben als Binärzahl ausgedrückt jeweils unendliche viele sich periodisch wiederholende Nachkommastellen. Diese müssen gerundet werden, was zu diesem Fehler führt.
Zusatzaufgaben: Mathematik
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
Muss Grössenordnung von $2^{1023}$ abschätzen.
Grösste Zahl ist $1.798 \cdot 10^{308}$. Mit Abschätzung ohne technische Hilfe sollte man auf etwa $10^{310}$ kommen.
Aufgabe:
Beweise, dass es im Interval $[0,1]$ überabzählbar unendlich viele reellen Zahlen gibt.
Lösungen LP
Lösungen LP
def binary_to_decimal(b):
n = len(b)
d = 0 # decimal number
for i in range(n):
d = d + int(b[i]) * 2**(n-i-1)
return d
def decimal_to_binary(d,bits=None):
b = ""
while d > 0:
b = str(d%2) + b
d //= 2
if bits:
while len(b) < bits:
b = '0' + b
return b
def find_mantissa_exponent(b,e,m):
b = b.lstrip('0')
pos_point = b.find('.')
first_one = b.find('1')
# exponent:
exp = pos_point - first_one
if exp > 0: exp -= 1
# bias
bias = 2**(e-1) - 1
exp_bias_bin = decimal_to_binary(exp+bias,e)
# mantissa
mantissa_bin = b.replace('.','')
mantissa_bin = mantissa_bin.lstrip('0')
mantissa_bin = mantissa_bin[1:m+1]
while len(mantissa_bin) < m:
mantissa_bin = mantissa_bin + '0'
return exp_bias_bin,mantissa_bin
print(find_mantissa_exponent("101.01",5,10))
print(find_mantissa_exponent("0.000101",5,10))
print(find_mantissa_exponent("0.1001",5,10))
Grösste und zweitgrösste Zahl für Float:
e = 8 # nr bits exponent
m = 23 # nr bits mantissa
exp_max = 2**(e-1)-1
nr_highest = 0
nr_2nd = 0
for i in range(exp_max-m+1,exp_max+1):
nr_2nd += 2**i
nr_highest = nr_2nd + 2**(exp_max-m)
print(nr_highest)
print(nr_2nd)
print(nr_highest-nr_2nd)