Zahldarstellung im Computer#
Speichereinheiten#
1 Bit= 1 b = kleinste Einheit, speichert \(0\) oder \(1\)
1 Byte= 1 B = Zusammenfassung von 8 Bit
1 Kilobyte= 1 KB = 1024 Byte
\(1024 = 2^{10}\) nächste 2er Potenz an \(1000\)
1 Megabyte= 1 MB = 1024 KB
1 Gigabyte= 1 GB = 1024 MB
1 Terabyte= 1 TB = 1024 GB
Speicherung von Zahlen#
Zur Speicherung von Zahlen wird je nach Datentyp fixe Anzahl an Bytes verwendet
Konsequenz:
pro Datentyp gibt es nur endlich viele Zahlen
es gibt jeweils größte und kleinste Zahl!
Ganzzahlen#
Mit \(n\) Bits kann man \( 2^n\) Ganzzahlen darstellen
Standardmäßig betrachtet man
entweder alle ganzen Zahlen in \([0,2^n-1]\)
oder alle ganzen Zahlen in \([-2^{n-1},2^{n-1}-1]\)
Integer-Arithmetik in Python#
Integer Arithmetik in Python unterscheidet sich von vielen andern Programmiersprachen, da es __keine zu großen Integer-Zahlen__gibt.
Python kann mit beliebig großen Zahlen exakt rechnen
Ausnahme: Speicherplatz im Computer reicht nicht um die Zahl zu speichern
Extrem unwahrscheinlich: 1 MB Speicher reicht für Zahlen mit ca. \(8\cdot 10^6\) binären Ziffern, also ca. \(2.4\cdot 10^6\) Dezimalziffern.
Begründung: 1 MB sind ca. 10^6 Bytes = \(8\cdot 10^6\) Bits
Ein Bit speichert eine binäre Ziffer
Um eine Dezimalziffer zu speichern, benötigt man ca. 3.32 Bits (\(\log_2(10) \approx 3.32\))
Viele andere Programmiersprachen (C, C++, Java, …) haben feste Grenzen für Integer-Zahlen
z.B. 32-Bit Integer: Zahlenbereich \([-2^{31},2^{31}-1] = [-2147483648, 2147483647]\)
z.B. 64-Bit Integer: Zahlenbereich \([-2^{63},2^{63}-1] = [-9223372036854775808, 9223372036854775807]\)
Werden diese Grenzen überschritten, kommt es zu einem Überlauf(engl. overflow)
z.B. in C/C++/Java: \(2147483647 + 1 = -2147483648\)
In Python gibt es keinen Überlauf, aber
Rechenoperationen mit sehr großen Zahlen sind deutlich langsamer als mit kleinen Zahlen
Achtung: Integer-Arithmetik in NumPy#
Aus Performance-Gründen verwendet die NumPy Bibliothek für numerische Rechnungen in Python standardmäßig 64-Bit Integer.
Exakte Arithmetik ist nur innerhalb des Bereichs \([-2^{63},2^{63}-1]\) gewährleistet.
Bei Überschreitung dieses Bereichs kommt es zu einem Überlauf, ähnlich wie in anderen Programmiersprachen.
Modulo-Arithmetik: größte Zahl + 1 = kleinste Zahl
Python gibt meistens eine Warnung aus, wenn ein Überlauf auftritt.
Es gibt auch die Möglichkeit, in NumPy mit größeren Integer-Typen zu rechnen, z.B. np.int128, wenn die Plattform dies unterstützt. Allerdings sind diese Typen nicht so weit verbreitet und können in manchen Umgebungen nicht verfügbar sein.
Kleinere Integer-Typen wie np.int8, np.int16, und np.int32 sind ebenfalls verfügbar, aber sie haben noch kleinere Wertebereiche und sind daher anfälliger für Überläufe.
- Rechnungen in kleineren Integer-Typen sind schneller als in größeren Integer-Typen.
import numpy as np
x = np.array([1, 2, 3], dtype=np.int64)
x[0] = 2**63-1
x[0] += 1
print(x[0])
-9223372036854775808
/var/folders/xh/ww_6450d6kv5hwq5w_j_kwjh0000gn/T/ipykernel_93262/104898551.py:5: RuntimeWarning: overflow encountered in scalar add
x[0] += 1
Beispiel: unsigned 4-bit Integer#
Um zu erklären, wie es zum Überlauf kommt, betrachten wir die Darstellung von Zahlen im Binärsystem mit 4 Bits (4 Speicherplätzen für 0/1).
4 Speicherplätze für 0/1 \(\implies\) 16 verschiedene Zahlen (\(0,\ldots, 15\))
Formel: \(a_3 a_2 a_1 a_0= \sum_{i=0}^3 a_i2^i\) mit \(a_i\in\{0,1\}\)
0 0 0 0\(= 0\)
0 0 0 1\(= 1\)
0 0 1 0\(= 2\)
0 0 1 1\(= 3\)
\(\ldots\)
1 0 0 0\(= 8\)
\(\ldots\)
1 1 1 1\(= 15\)
Addition wie im 10er System (mit Übertrag falls Ergebnis \(>1\))
0 0 0 1+ 0 1 0 1= 0 1 1 0 (\(1+5=6\))
Beispiel: 4-bit Integer#
4 Speicherplätze für 0/1 \(\implies\) 16 verschiedene Zahlen (\(-8,\ldots,0,\ldots, 7\))
0 0 0 0\(= 0\)
\(\ldots\)
0 1 1 1\(= 7\)
Beobachtung: 0 1 1 1+ 1 0 0 1= 0 0 0 0(\(+1\) Übertrag bleibt übrig)
daher: 1 0 0 1\(= -7\)
1 0 0 0\(= -8\)
\(\ldots\)
1 1 1 1\(= -1\)
Daher: 0 1 1 1+0 0 0 1= 1 0 0 0 (Überlauf \(7+1=-8\))
np.int32 hat nicht viel PLatz#
Kleinere Datentypen sind zwar schneller, haben aber deutlich weniger Platz für Zahlen.
Größter Wert für
np.int32: \(2^{31}-1 = 2147483647 \approx 2.1\) MilliardenAchtung:\(13! = 13\cdot 12! > 13\cdot 479\cdot10^6 > 6\) Millarden
import numpy as np
x = np.array([1], dtype=np.int32)
for i in range(17):
x[0] *= (i+1)
print(f"{i+1:2d}! = {x[0]}")
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
11! = 39916800
12! = 479001600
13! = 1932053504
14! = 1278945280
15! = 2004310016
16! = 2004189184
17! = -288522240
Achtung: Überlauf-Fehler wird zwar angezeigt, aber erst bei \(17!\). Das Ergebnis ist ab \(13!\) schon falsch.
Codierung von Zeichen#
Strings sind Folgen von Zeichen.
Jedes Zeichen wird durch eine Zahl codiert.
Festgelegt im Unicode-Standard (UTF-8)
Den Zahlencode eines Zeichen kann man mit der Funktion
ordherausfinden.Umgekehrt kann man mit der Funktion
chraus einem Zahlencode das entsprechende Zeichen zurückgewinnen.Oft wird der Unicode in hexadezimaler Schreibweise angegeben:
0x...
for c in "abcdefghijklmnopqrstuvwxyz":
print(f"The Unicode of '{c}' is {ord(c)}")
print("Cats are great " + chr(0x1F63B)*10)
The Unicode of 'a' is 97
The Unicode of 'b' is 98
The Unicode of 'c' is 99
The Unicode of 'd' is 100
The Unicode of 'e' is 101
The Unicode of 'f' is 102
The Unicode of 'g' is 103
The Unicode of 'h' is 104
The Unicode of 'i' is 105
The Unicode of 'j' is 106
The Unicode of 'k' is 107
The Unicode of 'l' is 108
The Unicode of 'm' is 109
The Unicode of 'n' is 110
The Unicode of 'o' is 111
The Unicode of 'p' is 112
The Unicode of 'q' is 113
The Unicode of 'r' is 114
The Unicode of 's' is 115
The Unicode of 't' is 116
The Unicode of 'u' is 117
The Unicode of 'v' is 118
The Unicode of 'w' is 119
The Unicode of 'x' is 120
The Unicode of 'y' is 121
The Unicode of 'z' is 122
Cats are great 😻😻😻😻😻😻😻😻😻😻
Gleitkommazahlen#
Reelle Zahlen werden im Computer in der Regel als Gleitkommazahlen (engl. floating point numbers) gespeichert. Der Name kommt daher, dass die Position des Dezimalpunkts (bzw. Binärpunkts) “gleiten” kann, abhängig von der Größe der Zahl.
Gleitkommadarstellung#
Folgender mathematischer Satz formalisiert die Idee der Gleitkommadarstellung:
SATZ: Zu \( x\in\mathbb{R}\) existieren
Vorzeichen \(\sigma\in\{\pm1\}\)
Ziffern \( a_k\in\{0,1\}\)
Exponent \( e\in\mathbb{Z}\) sodass gilt \(\displaystyle x = \sigma\Bigg(\sum_{k=1}^\infty a_k2^{-k}\Bigg)2^e\)
Darstellung ist nicht eindeutig, da z.B. \(1 = \displaystyle\sum_{k=1}^\infty2^{-k}\)
geometrische Reihe: \(\displaystyle \sum_{k=0}^\infty 2^{-k} = \frac{1}{1-1/2} = 2\)
z.B. \(\displaystyle \Bigg( \sum_{k=1}^\infty 2^{-k} \Bigg) 2^0 = \sum_{k=1}^\infty 2^{-k} = 1 = \Bigg( \sum_{k=2}^\infty 2^{-k} \Bigg) 2^1\)
Bemerkungen zum Dezimalsystem#
Satz gilt für jede Basis \( b\in\mathbb{N}_{\ge2}\)
Ziffern dann \( a_j\in\{0,1,\dots,b-1\}\)
Dezimalsystem \(b=10\) ist übliches System
\(47.11 = (4\cdot10^{-1}+7\cdot10^{-2}+1\cdot10^{-3}+1\cdot10^{-4})\cdot10^2\) * \(a_1=4\), \(a_2=7\), \(a_3=1\), \(a_4=1\), \(e=2\)
Nichteindeutigkeit im Dezimalsystem: \(0.\overline{9} = 1\)
Bemerkungen zum Binärsystem#
Mit \(b=2\) sind gekürzte Brüche genau dann als endliche Summe darstellbar (nicht als unendliche Reihe), wenn der Nenner Zweierpotenz ist:
\(\displaystyle\sum_{k=1}^{ M}a_k 2^{-k}\) hat Nenner mit Zweierpotenz
Folgt aus Eindeutigkeit der Primfaktorzerlegung
formaler Beweis = freiwillige Übung
z.B. keine exakte Darstellung für \(1/10\) für \(b=2\)
Definition von Gleitkommazahlensystemen#
Gleitkommazahlsystem \(\mathbb{F}(2,M,e_{\rm min},e_{\rm max})\subset\mathbb{Q}\)
endliche Teilmenge!
Mantissenlänge \( M\in\mathbb{N}\)
Exponentialschranken \( e_{\rm min}<0<e_{\rm max}\)
\( x\in\mathbb{F}\) hat Darstellung \(\displaystyle x = \sigma\Bigg(\sum_{k=1}^M a_k2^{-k}\Bigg)2^e\) mit
Vorzeichen \(\sigma\in\{\pm1\}\)
Ziffern \( a_j\in\{0,1\}\) mit \( a_1=1\) oder \( e = e_{\rm min}\) * normalisierte Gleitkommazahl (\( a_1=1\)) * denormalisierte Gleitkommazahl (\( e = e_{\rm min}\))
Exponent \( e\in\mathbb{Z}\) mit \( e_{\rm min}\le e\le e_{\rm max}\)
Darstellung von \( x\in\mathbb{F}\) ist eindeutig (Übung!)
Ziffer \(a_1\) muss nicht gespeichert werden
implizites erstes Bit
Arithmetik für Gleitkommazahlen#
Ergebnis Inf, __-Inf__bei Überlauf (oder 1./0.)
\(+\infty\) (Inf), \(-\infty\) (-Inf)
Ergebnis NaN, falls nicht definiert (z.B. 0./0.)
not a number (NaN)
Arithmetik ist approximativ, nicht exakt
Schlechte Kondition#
Eine Aufgabe ist __numerisch schlecht gestellt__bzw. schlecht konditioniert, falls kleine Änderungen der Daten auf große Änderungen im Ergebnis führen
z.B. hat Dreieck mit gegebenen Seitenlängen einen rechten Winkel?
z.B. liegt gegebener Punkt auf Kreisrand?
Implementierung sinnlos, weil Ergebnis zufällig!
bessere Fragestellungen:
Hat ein Dreieck mit gegebenen Seitenlängen einen Winkel \(\approx 90^\circ\)?
Liegt ein Punkt in einem \(\varepsilon\)-Schlauch um den Kreisrand?
Rechenfehler#
Aufgrund von Rechenfehlern darf man Gleitkommazahlen nie auf Gleichheit überprüfen - Statt \(x=y\) prüfen, ob Fehler \(|x-y|\) klein ist - z.B. \(|x-y|\le\varepsilon\cdot\max\{|x|,|y|\}\) mit \(\varepsilon=10^{-13}\)
In
NumPygibt es die Funktionnp.isclose(x, y, atol=..., rtol=...), die prüft, ob zwei Gleitkommazahlenxundy“nahe beieinander” liegen. Dabei sindatolundrtolabsolute bzw. relative Toleranzen.Die Funktion überprüft, ob die Bedingung
abs(x - y) <= atol + rtol * abs(y)erfüllt ist.
print("0.1 + 0.2 = 0.3?", 0.1 + 0.2 == 0.3)
print(f"0.1 is actually {0.1:.30f}")
print("(116/100)*100 = 116", (116./100)*100 == 116)
print(f"(116/100)*100 is actually {(116./100)*100:.30f}")
print("0.1 + 0.2 = 0.3 up to round-off error?", np.isclose(0.1 + 0.2, 0.3))
print("(116/100)*100 = 116 up to round-off error?", np.isclose((116./100)*100, 116))
0.1 + 0.2 = 0.3? False
0.1 is actually 0.100000000000000005551115123126
(116/100)*100 = 116 False
(116/100)*100 is actually 115.999999999999985789145284797996
0.1 + 0.2 = 0.3 up to round-off error? True
(116/100)*100 = 116 up to round-off error? True
Variablentypen np.float32, np.float64#
np.float32ist idR. einfache Genauigkeit nach IEEE-754-Standard\(\mathbb{F}(2,24,-126,127)\) \(\to\) 4 Byte = 32 Bit * 32b = 8b Exp. + 23b Mantisse + 1b Vorz.
sog. single precision
ca. 7 signifikante Dezimalstellen
np.float64ist idR. doppelte Genauigkeit nach IEEE-754-Standard\(\mathbb{F}(2,53,-1022,1023)\) \(\to\) 8 Byte = 64 Bit * 64b = 11b Exp. + 52b Mantisse + 1b Vorz.
sog. double precision
ca. 16 signifikante Dezimalstellen
x32 = np.array(0.1, dtype=np.float32)
x64 = np.array(0.1, dtype=np.float64)
print(f"float32: 0.1 is actually {x32:.30f}")
print(f"float64: 0.1 is actually {x64:.30f}")
float32: 0.1 is actually 0.100000001490116119384765625000
float64: 0.1 is actually 0.100000000000000005551115123126