v02 (Elementare nume..

Werbung
Vorlesung
2
Inhalt
1 Kodierung numerischer Datentypen
1.1 Zahlen und Zahlendarstellungen
1.2 Ganze Zahlen (Integer) als Dualzahlen
1.2.1 Dualdarstellung
1.2.2 Kodierung negativer Zahlen
1.2.3 Little und Big Endian
1.3 Gleitkommazahlen (floating point number)
1.3.1 Das IEEE 754-Format
1.3.2 Umwandlung zu IEEE 754
1.3.3 Genauigkeitsprobleme mit Gleitpunktzahlen
1.4 Alternative Dezimalzahlendarstellungen
1.4.1 BCD-Code
1.4.2 IEEE 854
2 Objekte in Python
2.1 Typing in Python
2.2 Bezeichner
2.2.1 Syntax
2.2.2 Konventionen
3 Elementare numerische Datentypen in Python
3.1 Numerische Datentypen
3.2 Operationen auf Zahlen
3.3 Weitere Operatoren und mathematische Funktionen
3.4 Die streng objektorientierte Implementierung von Python
4 Vergleiche und Boolesche Ausdrücke in Python
4.1 Ausdrücke
4.2 Vergleichsoperationen
4.3 Boolsche Ausdrücke
1
2
2
3
5
6
7
9
12
12
13
13
14
15
16
17
17
18
20
20
23
26
26
27
27
28
29
5 Zuweisungen von Objekten
5.1 Einfache Zuweisungen
5.2 Erweiterte Zuweisung
6 Reading
Anhang 1 Charakterisierung von Zahlen
31
31
32
33
34
V O R L E S U N G E N 2 :
E L E M E M E N T A R E N U M E R I S C H E
D A T E N T Y P E N
Vorlesung
2
V2
Elementare numerische
Datentypen
Lernziele: Numerische Datentypen sind neben Zeichenketten die wichtigsten
elementaren Datentypen: Integer, Float, Decimal sollen verstanden werden –
einschließlich ihrer numerischen Besonderheiten.
Außerdem werden wir lernen, Ausdrücke aus den Zahlen und Variablen zu
bilden und ihren Wert zu bestimmen.
Für die ersten Programmierschritte ist ein genaues Verständnis des Zuweisungsoperators und dessen Realisierung in Python unverzichtbar.
1 Kodierung numerischer Datentypen
Von allen verschiedenen menschlichen Möglichkeiten, Informationen zu kodieren, wie Sprache und Text, Tabellen, Graphiken und Bilder, Musik und Geräusche, sind zweifellos Zahlen
und (Schrift-)Zeichen elementar. Jede übliche Programmiersprache stellt diese Datentypen als
elementare Datentypen zur Verfügung. Nach dem vorherigen Kapitel würde es auch ausreichen, diese Datentypen abstrakt zu beschreiben, um mit ihnen umzugehen. In einigen Fällen
ist es aber wichtig, die konkrete Repräsentation eines solchen Datentyps zu kennen – zumindest die Prinzipien, denn aus dieser Kenntnis lassen sich Eigentümlichkeiten dieser konkreten
Datentypen ableiten, die abstrakt beschrieben viel zu kompliziert wären.
Bei Formulierungen in Programmiersprachen versucht man dabei, die üblichen Notationen
der Mathematik weitgehend beizubehalten, in der Regel die angloamerikanischen Konventionen.
Es lohnt sich, die mathematischen Grundlagen kurz zu rekapitulieren, bevor wir die Rechnerrepräsentationen betrachten. (siehe auch den Anhang).
1
1.1 Zahlen und Zahlendarstellungen
Aus der Mathematik sind uns insbesondere folgende Zahlenmengen geläufig:1
•
Die Menge der Natürlichen Zahlen
N oder
•
Die Menge der Ganzen Zahlen
Z oder
•
Die Menge der Rationalen Zahlen
Q oder
•
Die Menge der Reellen Zahlen
R oder
•
Die Menge der Komplexen Zahlen
C oder
•
(Die Menge der Quaternionen)
H oder
)
Allgemein gilt: N ⊂ Z ⊂ Q ⊂ R ⊂ C ⊂ H
Dabei schreiben wir diese Zahlen gewöhnlich als vorzeichenbehaftete Dezimalzahl, also in
einem Stellenwertsystem zur Basis 10. Beispiele sind:
0, -42, 1,414, 3,141, -1,59, 1,43 ⋅ 10 6 oder 0, -42, 1.414, 3.141, -1,59, 1.43 ⋅ 10 6
Anstelle des Kommas als Dezimaltrenner (es kennzeichnet die Stelle (100, 10-1) zwischen den Zehnerpotenzen) verwendet man im angloamerikanischen Raum den Punkt – diese Punkt-Schreibweise findet
sich in allen Programmiersprachen wieder!
Für diese Zahlenmengen benötigen wir geeignete rechnerinterne Kodierungen, und zwar solche, auf denen die üblichen Operationen effizient ausgeführt werden können. Da die meisten
Rechnersysteme auf Schaltkreisen aufbauen, die zwei verschiedene Zustände annehmen können, biete sich die Darstellung der Zahlen durch Dualzahlen an. Insbesondere findet sie bei
der Darstellung von natürlichen Zahlen und ganzen Zahlen Verwendung. Negative Zahlen
werden dabei als sog. 2-Komplement dargestellt, siehe unten. Um näherungsweise rationale
oder gar reelle Zahlen darzustellen, werden vorzugsweise Fließkommadarstellungen verwendet, bei der die Zahl normalisiert und in sog. Mantisse und Exponent aufgeteilt und beide
Werte dann in Form von Dualzahlen gespeichert werden. Genaueres wird im Folgenden beschrieben.
1.2 Ganze Zahlen (Integer) als Dualzahlen
Bevor wir die vorzeichenbehafteten ganzen Zahlen betrachten, fangen wir zunächst nur mit
natürliche Zahlen an und stellen diese als Dualzahlen (Binärzahlen), also anstelle zur Basis 10
jetzt zur Basis 2 dar.
Die Symbole für die Zahlenmengen N, Z, Q, R, C (deutlich fett gedruckt) verdanken wir „Nicolas Bourbaki“ , ebenso wie z.B. die Schreibweise für die leere Menge Ø. Nicolas Bourbaki ist
ein Pseudonym für eine Gruppe zumeist französischer Mathematiker, die in der Zeit von 1934
bis ca. 1980 an einem Lehrbuch der Mathematik, den Éléments de Mathématique arbeiteten. Weil
dieser „Fettdruck“ handschriftlich nur schwer darstellbar ist, variierte man es in Handschriften
und im Tafelbild zu dem „Strichbuchstaben“:
, , ,
,
. Im Laufe der Zeit wurde diese
Schreibweise gegenüber dem fett gedruckten zunehmend auch im Drucksatz benutzt und hat sich
mittlerweile fast völlig durchgesetzt. Leider hat Microsoft im Formeleditor 3.0 dieses noch nicht
mitbekommen und deshalb benutzen wir aus Bequemlichkeit den Fettdruck.
1
2
1.2.1
Dualdarstellung
Die Dyadik (dyo, griech. = Zwei), also die Darstellung von Zahlen im Dualsystem, wurde
schon Ende des 17. Jahrhunderts von Leibniz entwickelt.2 Das Dualsystem ist ein vollständiges Zahlensystem mit der geringstmöglichen Anzahl an verschiedenen Ziffern. Der Nachteil
des Zweiersystems sind jedoch die langen Zeichenreihen, die sich dabei ergeben.
Eine Dualzahl wird durch die Ziffern z i ∈ {0,1} dargestellt. Die Ziffern werden wie im Dezimalsystem ohne Trennzeichen hintereinander geschrieben, ihr Stellenwert entspricht allerdings der zur Stelle passenden Zweierpotenz und nicht der Zehnerpotenz.
Es wird also bei m Stellen die höchstwertige Stelle mit dem Wert zm-1 ganz links und die niederwertigeren Stellen mit den Werten zn-2 bis z0 in absteigender Reihenfolge rechts davon aufgeschrieben:
z n −1z n − 2 ...z1z 0
Der Wert Z der Dualzahl ergibt sich durch Addition dieser Ziffern, welche vorher jeweils mit
ihrem Stellenwert 2i multipliziert werden:
n −1
Z = ∑ z i ⋅ 2i
i=0
Beispiel: Die Ziffernfolge 1101 stellt im Dualsystem nicht die Tausendeinhundertundeins,
sondern die Dreizehn des Dezimalsystems dar. Aus dem Dualsystem berechnet sich
der Wert fürs Dezimalsystem durch
[1101]2 = 1 ⋅ 2 3 + 1 ⋅ 2 2 + 0 ⋅ 21 + 1 ⋅ 2 0 = [13]10
Die Klammerung der Resultate mit der tiefgestellten dezimalen 2 beziehungsweise der dezimalen 10 gibt die Basis des verwendeten Stellenwertsystems an. In der Literatur werden die eckigen Klammern oft weggelassen und die tiefer gestellte Zahl wird dann manchmal in runde
Klammern gesetzt, also:
1101( 2 ) = 13 (10 )
Ebenfalls üblich ist die Kennzeichnung durch den nachgestellten Großbuchstaben B (für binär).
Wie wandelt man eine Dezimalzahl in eine Dualzahl um? Der Algorithmus hängt davon ab, in
welchem Stellenwertsystem wir rechnen wollen. Wenn wir dieses „von Hand“ ausführen wollen, wählen wir aufgrund der Vertrautheit beim Rechnen zweckmäßigerweise das Dezimalsystem.
Leibnitz sah das Dualsystem ein besonders überzeugendes Sinnbild des christlichen Glaubens an. So
schrieb er an den chinesischen Kaiser Kangxi: "Zu Beginn des ersten Tages war die 1, das heißt Gott. Zu Beginn
des zweiten Tages die 2, denn Himmel und Erde wurden während des ersten geschaffen. Schließlich zu Beginn des siebenten
Tages war schon alles da; deshalb ist der letzte Tag der vollkommenste und der Sabbat, denn an ihm ist alles geschaffen und
erfüllt, und deshalb schreibt sich die 7 als 111, also ohne Null. Und nur wenn man die Zahlen bloß mit 0 und 1 schreibt,
erkennt man die Vollkommenheit des siebenten Tages, der als heilig gilt, und von dem noch bemerkenswert ist, dass seine
Charaktere einen Bezug zur Dreifaltigkeit haben."
2
3
Zwei Verfahren zur Wandlung „von Hand“ sind üblich.
Verfahren 1:
Schritt 1. Man berechne alle Potenzen von b = 2 bis die größte Potenz größer als die umzuwandelnde Zahl ist. Also
20 = 1, 21 = 2, 2 2 = 4, 23 = 8, 2 4 = 16, 25 = 32, 2 6 = 64
Schritt 2. Man zerlege die umzuwandelnde Zahl, beispielsweise die „42“, in Potenzen der Basis
42(10) = 1⋅ 32 + 10 = 25 + 10
= 25 + 8 + 2 = 25 + 23 + 2 = 25 + 23 + 2
= 1 ⋅ 25 + 0 ⋅ 24 + 1⋅ 23 + 0 ⋅ 22 + 1⋅ 21 + 0 ⋅ 20
= 1010102
Verfahren 2: Man errechne die gesuchte Darstellung durch fortgesetzte Division mit Rest
durch b: der erste Rest ergibt z0, der zweite z1, … usw.
42/2 = 21
21/2 = 10
10/2 = 5
5/2 = 2
2/2 = 1
1/2 = 0
Rest 0 (Low Significant Bit)
Rest 1
Rest 0
Rest 1
Rest 0
Rest 1 (High Significant Bit)
also 42(10) = 101010 ( 2)
Neben den Basen 2 und 10 sind auch vielfache von 2, nämlich 8 (Oktalsystem) und 16 (Hexadezimalsystem, Sedezimalsystem).üblich. Beim Hexadezimalsystem erweitert man das bekannte Ziffernsystem um die Großbuchstaben A bis F, also {0,1,2,….9,A,B,C,D,E,F} um für die
dezimalen Zahlen 10,…,15 nur eine Ziffer zu haben.
Nützlich sind diese insbesondere für kurze Zeichenketten bei einer „QuasiDualzahlendarstellung“: beim Oktalsystem werden 3 Dualstellen zu einer Oktalziffer, beim
Hexadezimalsystem 4 Dualstellen zu einer Hexadezimalstelle zusammengefasst. Unser Beispiel
sieht dabei so aus:
42(10) = 101 010(2) = 52(8)
für die Oktaldarstellung
= 0010 1010(2) = 2A (16) für die Hex-Darstellung
Der tatsächliche Wert einer Zahlendarstellung kann also nur mit der Kenntnis der Basis entschieden werden.3
In der Hardware ist die Verarbeitungsbreite, also die Stellenzahl n, starr festgelegt. Üblich sind
heute 32 Bit (32 Stellen) und zunehmend häufiger 64 Bit; kleine Zahlen haben also in diesem
System viele führende Nullen.
3 Warum
können amerikanische Informatiker (Computer Scientists) Weihnachten (25. Dezember) nicht von
Halloween (31. Oktober) unterscheiden?
(Antwort: Weil 31(oct) = 25(dez)) ;-)
4
1.2.2
Darstellung auch negativer Zahlen
Um auch negative Zahlen darstellen zu können, wird das höchstwertigste Bit als Vorzeichenbit
interpretiert: Es ist null bei positiven Zahlen und eins bei negativen. Damit wird der Bereich
der verarbeitbaren positiven Zahlen eingeschränkt und die frei werdenden Kodeworte für negative Zahlen genutzt. Das für die Zahlendarstellung fast ausschließlich genutzte Verfahren ist
das Zweierkomplement4 (auch echtes Komplement, 2-Komplement, Zweikomplement, K2Zahl, 2K-Zahl). Es hat die Eigenschaft, dass die Addition aus der negativen Zahl und der positiven Darstellung als Binärzahl genau null ergibt.
Wie erhalten wir ein solches Zweierkomplement? Voraussetzung ist eine feste Stellenzahl, sagen wir für das folgende Beispiel m = 8 Stellen. Negative Zahlen werden mit einer führenden
1 dargestellt und wie folgt kodiert: Sämtliche Ziffern der entsprechenden positiven Zahl werden invertiert; es wird das binäre Komplement gebildet. Zum Ergebnis wird dann noch 1 addiert.
Beispiel zur Umwandlung einer negativen Dezimalzahl ins Zweierkomplement, hier –4
1. Vorzeichen ignorieren und ins Binärsystem umrechnen: 4(10) = 00000100(2)
2. Invertieren, da negativ: 11111011
3. Eins addieren: 11111011 + 00000001 = 11111100
Hierzu muss man allerdings im Binärsystem addieren können (dies lernen Sie erst in Elektrotechnische und Digitaltechnische Grundlagen), darum eine kleine Modifikation des 2. Schritts:
2. Beginnen Sie das Invertieren mit der niedrigst-wertigen Stelle z0: Invertieren Sie die
Stellen fortschreitend bis zur ersten Null, diese wird noch invertierte, die restlichen
Stellen bleiben unverändert. Schritt 3. entfällt.
Mit n Bits lassen sich so Zahlen negative und positive Zahlen von –2 n-1 bis +2 n-1 – 1 darstellen, also z.B. bei 32 Bit: –2.147.483.648(10) bis +2.147.483.647(10).
Wenn man eine Zahl vom Zweierkomplement ins Dezimalsystem umkodieren will, kehrt man
das oben dargelegte Verfahren um, also man subtrahiert 1, usw. Auch dieses lässt sich vereinfachen: Man invertiert zuerst die einzelnen Ziffern, wandelt in eine Dezimalzahl und addiert
hinterher 1, was zu demselben Ergebnis führt.
Positive Zahlen werden im Zweierkomplement mit einer führenden binären 0 versehen und
ansonsten nicht verändert.
Mit den hier vorgestellten Kodierungen lassen sich alle Grundoperationen, also insbesondere
+, -, x und / (Festpunktdivision) effizient realisieren. Vom algebraischen Standpunkt mangelt
es allerdings durch die Stellenbegrenzung (z.B. n = 32 ) an der algebraischen Abgeschlossenheit, d.h. Addition, Subtraktion und Multiplikation können Ergebnisse haben, die außerhalb
des darstellbaren Zahlenbereichs liegen. Diese Situation bezeichnet man als Überlauf. So kann
die Addition von 2 größeren positiven Zahlen zu einer 1 in der ersten Stelle führen, was dem
Kodewort einer negativen Zahl entspricht. Dieses Ereignis muss dem Programm signalisiert
4 Ein weiteres Verfahren ist das sog. Einerkomplement; hierbei wird für eine negative Zahl die Kodierung
der entsprechenden positiven Zahl nur stellenweise invertiert, sonst nichts. Addiert man die positive Zahl
auf die negative, so ergibt sich eine Zahl nur aus Einsen: die Null hat also zwei Repräsentationen, es gibt also
-0 und +0), und die Arithmetik wird etwas komplizierter.
5
werden, damit entsprechende Maßnahmen ergriffen werden können – erfolgt i.d.R. durch sogenannte Exceptions (Ausnahmen), die wir später besprechen.
1.2.3
Little und Big Endian
Leider gibt es noch eine weiteres Detail zu beachten: Die Byte-Reihenfolge (Byte order). Im
Speicher eines Rechners sind die Speicherzellen in Bytes organisiert und in der Regel auch so
adressierbar: Wenn zum Beispiel die 32 Bit Binärzahl, hier in Hex-Schreibweise A4B3C2D1
im Hauptspeicher abgelegt wird, so gibt es verschiedene Möglichkeiten:
Adresse
(Speicheroffset)
0
1
2
3
little endian
big endian
D1
C2
B3
A4
A4
B3
C2
D1
Middle Endian
(obsolet)
C2
B3
D1
A4
A4
D1
B3
C2
Tabelle 1: Alternative Byte-Reihenfolgen (Byte order)
Wenn wir die 4 Bytes in der Reihenfolge D1 C2 B3 A4, also das niederwertigste Byte an der
niedrigsten Speicheradresse ablegen, spricht man von little endian. Die Speicherung in der
Reihenfolge A4 B3 C2 D1, also das höchstwertige Byte an der niedrigsten Speicheradresse,
wird als big endian bezeichnet. Die Middle Endian-Varianten sind heute veraltet.
Durch die Kennzeichnung (und Beachtung!) beider Formate kann man den Streit vermeiden,
welches denn nun die „richtige“ Darstellung sei. In die Fachterminologie eingeführt wurden
diese Begriffe 1980 von Danny Cohen in seiner Internet Engineering Note 137: ''On Holy
Wars and a Plea for Peace'', siehe Readings zu dieser Vorlesung. Als Fachbegriffe wird auch im
Deutschen little-endian und big-endian benutzt. Übersetzungen sind nicht üblich.
Die Etymologie (Herkunft) dieser kuriosen Bezeichnungen „Endian“ ist interessant: Sie lehnen
sich an den satirischen Roman Gullivers Reisen („Gulliver's Travels“) von Jonathan Swift (1726) an,
in dem der Streit darüber, ob ein Ei am spitzen oder am dicken Ende aufzuschlagen sei, die Bewohner von Liliput in zwei verfeindete Lager spaltet – die „Little-Endians“ und die „Big-Endians“,
in der deutschen Übersetzung des Buches übrigens „Spitz-Ender“ und „Dick-Ender“. 5
Auf Programmiersprachenebene hat diese Unterscheidung keine Bedeutung, weil die Interpretation ( = „Abstraktion“) z.B. dieser 4 Bytes als Binärzahl immer in gleicher Weise erfolgt.
Relevant ist die Unterscheidung unter Umständen im Bereich der Systemprogrammierung,
z.B. wenn man Geräte ansteuern will, und insbesondere beim Datenaustausch über Netzwerke
oder über Speichermedien.
Im Internet ist das big-endian als Network Byte Order festgelegt. Die sogenannte Host Byte Order
ist aber bei heute gebräuchlichen Systemen verschieden:6
6 Scherzhaft wird das Problem verschiedener Endians auch als „NUXI-Problem“ bezeichnet: Wenn das
Wort UNIX in zwei 2-Byte-Worten gespeichert wird, liegt es in einem Big-Endian-System als „UNIX“
im Speicher, in einem Middle-Endian-System (PDP-11, rechte Spalte) dagegen wegen der Vertauschung
der Bytes in jedem Wort als „NUXI“.
6
Little Endian:
Intel-x86-Prozessoren und auch das Betriebssystem Windows
Big Endian;
Power PC (umschaltbar), Motorola-68000-Familie, MIPS Prozessoren,
HP-UX, Internet Protokoll (IP)
1.3 Gleitkommazahlen (floating point number)
Die Darstellung der Kommazahlen im Dezimalsystem kennen wir aus dem Alltag. Beispielsweise bedeutet
3,75 = 3 + 0,7 + 0,05 = 3⋅100 + 7⋅10-1 + 5⋅10-2
Dies können wir auf Dualzahlen verallgemeinern. Obige Zahl ist dann
3,75(10) = 2 + 1 + 0,5 + 0,25 = 1⋅21 + 1⋅20 + 1⋅2-1 + 1⋅2-2 = 11,111(2)
und allgemein ist die Kommazahl F mit n Stellen vor dem Komma und p Stellen nach dem
Komma für die Basis b
−p
F=
∑z
i=n
i
⋅ bi
Umrechnung ins Binärsystem. Angenommen wir haben die Kommadarstellung einer Zahl
F = 42,8125 im Dezimalsystem. Wie erhalten wir die entsprechende Dualzahl? Aus der obigen
Darstellung sehen wir, dass die resultierende Kommazahl eine Überlagerung aus der Zahl vor
dem Komma und der nach dem Komma darstellt. Also teilen wir die Kommazahl auf: Der
Teil vor dem Komma (hier: 42) wird als ganze Zahl behandelt und mit dem Verfahren 1 oder
2 des vorigen Abschnitts 1.2.1 umgewandelt. Wie erhalten wir den Teil nach dem Komma?
Dazu modifizieren wir die beiden genannten Verfahren entsprechend:
Verfahren 3:
Schritt 1. Man berechne alle Potenzen von b = 2, bis die kleinster Potenz kleiner als die umzuwandelnde Zahl ist. Also
2 −1 = 0,5, 2 −2 = 0, 25, 2 −3 = 0,125, 2 −4 = 0, 0625, 2 −5 = 0, 03125, 2 −6 = 0, 015625, ...
Schritt 2. Man zerlege die umzuwandelnde Zahl, beispielsweise die „0,8125“, in Potenzen der
Basis
0,8125(10) = 1⋅ 0,5 + 1 ⋅ 0, 25 + 0 ⋅ 0,125 + 1⋅ 0, 0625
= 1 ⋅ 2−1 + 1 ⋅ 2−2 + 0 ⋅ 2−3 + 1⋅ 2−4
= 0,1101( 2)
Verfahren 4: Man errechnet die gesuchte Darstellung durch fortgesetzte Multiplikation mit b
und Rest. Ist das Ergebnis größer eins, so kann man die eins (=b0) abziehen und den Rest wieder mit b multiplizieren. solange, bis kein Rest mehr bleibt Das erste Ergebnis ergibt z-1, der
zweite z-2, … usw.
0,8125⋅2 = 1,625 = 1 Rest 0,625 (High Significant Bit)
0,625 ⋅2 = 1,25 = 1 Rest 0,25
0,25 ⋅2 = 0,5 = 0 Rest 0,5
7
0,5 ⋅2 = 1,0 = 1 Rest 0,0
also 0,8125(10) = 0,1101( 2)
(Low Significant Bit)
Zusammen erhalten wir für die umgewandelte Zahl
42,8125 = 101010 ,1101
Im Unterschied zu Kommazahlen haben Gleitkommazahlen (auch: Gleitpunktzahl,
manchmal auch Fließkommazahl; halblogarithmische Darstellung, engl: floating point number) ein
normiertes Format der Darstellung, etwa genau eine Stelle vor dem Komma und p Stellen
nach dem Komma. Bei der Gleitkommadarstellung wird sehr ähnlich der üblichen wissenschaftlichen Schreibweise sehr großer oder sehr kleiner Zahlen, wie z.B. 8,432⋅1023, zur Kodierung einer Zahl eine Mantisse7 m (hier: m = 8,432) und ein Exponent e (hier: e = 23) zu einer bestimmten, festen Basis b (hier: b = 10) gespeichert. Gegenüber einer Integer-Darstellung
wird so bei gleichem Speicherplatzbedarf ein viel größerer Wertebereich abgedeckt.
DEF Eine Zahl a ≠ 0 wird durch zwei Zahlen m und e solcherart dargestellt, dass a = m — be
gilt. Dabei ist die Basis b (auch: Radix) eine beliebige natürliche Zahl > 1. Die Zahl m
wird „Mantisse“ genannt und ist eine Zahl mit p Stellen (der sogenannten Präzision,
engl. precision) der Form ±z0,z-1z-2... zp-1 . Hierbei steht z für eine Ziffer zwischen 0 und b
– 1.
Liegt die Mantisse im Wertebereich 1 < m < b-1 (im Fall b=2 ist die Vorkommazahl 1), so
spricht man von einer normalisierten Mantisse. Liegt die Mantisse im Wertebereich 1/b <
m < 1 (im Fall b=2 ist die Vorkommazahl 0 und die erste Nachkommastelle ist ungleich 0), so
spricht man von einer normierten Mantisse (0.xxxx-Form).
Die Normalisierung oder Normierung wird durch Anpassung des (ganzzahligen) Exponenten
e erreicht.
Bei Gleitkommazahlen ist also nicht die absolute Anzahl von Dezimalstellen konstant, sondern die Anzahl wesentlicher oder signifikanter Stellen, die durch die Präzision (Anzahl der
Bits zur Darstellung der Mantisse) bestimmt wird.
Beispiel: Eine Gleitkommazahl mit vier dezimalen Stellen (b = 10, p = 4) kann dazu verwendet werden, 4,321 oder 0,00004321 darzustellen. Es muss allerdings in Kauf genommen
werden, dass bei einer derartigen Darstellung Zahlen zu runden sind. So wird etwa aus
432,123 der Wert 432,1 und aus 43.212,3 der Wert 43.210.
Eine Gleitkommazahl ist eine exakte, meist aber approximierte Kodierung einer rationalen
Zahl in einer festgelegten Anzahl von Bits (meist 32, 64, seltener 128 oder gar 256 Bits). Gleitkommazahlen werden auch als rationale Näherungen für reelle Zahlen genutzt. Die Menge der
Gleitkommazahlen ist somit eine endliche Teilmenge der rationalen Zahlen, meist erweitert
um einige Spezialelemente ( + ∞ , − ∞ , NaN (="Not A Number"), –0, usw., siehe unten).
Zusammen mit den auf ihnen definierten Operationen bilden die Gleitkommazahlen eine endliche Arithmetik, die vor allem im Hinblick auf numerische Berechnungen entwickelt wurde.8
Der aus dem lateinischen kommende Begriff Mantisse ist die Bezeichnung für Nachkommastellen. Bekannt ist der Begriff vor allem durch das Logarithmieren. In der Informatik sind die Mantissen für die Darstellung von Gleitkommazahlen von herausragender Bedeutung.
7
Die Gleitkommadarstellung wurde schon von Konrad Zuse in den dreißiger Jahren für seine Computer Z1
und Z3 beschrieben.
8
8
1.3.1
Das IEEE 754-Format
Das gebräuchliche und häufig auch durch Hardware unterstützte Format ist in der Norm
IEEE 754 (ANSI/IEEE Std 754-1985; IEC-60559 - International Version) festgelegt. Diese
Norm legt die Standarddarstellungen für binäre Gleitkommazahlen fest und definiert Verfahren für die Durchführung mathematischer Operationen, insbesondere für Rundungen. Beinahe alle modernen Prozessoren folgen diesem Standard. Ausnahmen sind einige IBMGroßrechnersysteme, die VAX-Architektur und einige Supercomputer, etwa von Cray sowie
die Java Virtual Machine mit den Java Typen float und double, die nur einen Subset der IEEE
754 Funktionalität unterstützt.9
In der IEEE 754-Norm sind zwei Grunddatenformate für binäre Gleitkommazahlen mit 32
Bit („single precision“) bzw. 64 Bit („double precision“) Speicherbedarf und zwei erweiterte
Formate definiert. (In der verwandten Norm IEEE 854 werden nichtbinäre Gleitpunktzahlen
definiert.) Im Entwurf für eine Neufassung von IEEE 754 (IEEE 754r) werden weitere binäre (16, 32, 64, 128 Bit) und dezimale (32, 64, 128 Bit) Formate für Gleitpunktzahlen vorgeschlagen. IEEE 754 und IEEE 854 werden in Zukunft also zusammengeführt werden.
Bei normalisierten Gleitkommazahlen (NZ) nach IEEE 754 ist die Basis b = 2. Das Vorzeichen s = ( − 1)S wird in einem Bit S gespeichert, so dass S = 0 positive Zahlen und S = 1 negative Zahlen markiert. Der Exponent e ergibt sich aus der in den Exponentenbits gespeicherten nichtnegativen Binärzahl E durch Subtraktion eines festen Biaswertes B: e = E − B.
Schließlich ist die Mantisse 1 < m < 2 ein Wert, der sich aus den p Mantissenbits M berechnet
als m = 1 + M / 2p. Einfacher ausgedrückt, denkt man sich an das Mantissenbitmuster M links
eine „1.“ angehängt: m = 1.M.
s = ( − 1)S
e=E−B
m = 1.M = 1 + M / 2p
Dieses Verfahren ist möglich, weil durch Normalisierung die Bedingung 1 < m < 2 für alle
(darstellbaren) Zahlen immer eingehalten werden kann. Da dann die Mantisse immer links mit
„1.“ beginnt, braucht dieses Bit nicht mehr gespeichert zu werden. Man lässt dieses Bit also
einfach weg (hidden Bit). Damit gewinnt man ein zusätzliches Bit Genauigkeit (Präsision).
Neben normalisierten Zahlen sind in IEEE 754 auch nicht-normalisierte Zahlen (denormalized
numbers) zugelassen, siehe unten.
Schließlich gibt es spezielle Werte, die Sonderfälle kennzeichnen. Dazu gehört die Zahl 0 in
zwei Darstellungen +0 und -0. Des Weiteren + ∞ und − ∞ .
Ferner gibt es spezielle Darstellungen für Nichtzahlen, bezeichnet als NaN (not a number), mit
denen explizit Ergebnisse verbotener Operationen markiert werden können. NaNs werden in
Signal-NaNs (signaling NaN) für die Erzeugung von Ausnahmebedingungen (exceptions) und
stille NaNs (quiet NaN) unterteilt.
9 Hierzu gab es hitzige Fachdiskussionen, vergl. z.B. „How Java’s Floating-Point Hurts Everyone Everywhere“ by W. Kahan and D. Darcy, 1998, siehe http://www.cs.berkeley.edu/~wkahan/ JAVAhurt.pdf und
auch Lösungsansätze, siehe http://www.sonic.net/~jddarcy/Borneo/. Eines der Probleme dreht sich um
NaN. Es gibt eine Vielzahl von Ursachen, warum ein Rechenergebnis NaN ist und entsprechend in IEEE
754 eine Vielzahl verschiedener NaN-Darstellungen, die in Java einfach auf einen einzigen NaN-Wert abgebildet werden.
9
IEEE 754 unterscheidet vier Darstellungen: einfach genaue (single), erweiterte einfach genaue (single extended), doppelt genaue (double) und erweiterte doppelt genaue (double extended) Zahlenformate, wobei i.d.R. nur single und double genutzt werden. Bei
den erweiterten Formaten ist nur jeweils eine Mindestbitzahl vorgeschrieben. Die Grundformate sind vollständig definiert.
Die Anzahl der Mantissenbits legt die Genauigkeit der Zahlen fest. .Die Anzahl der Exponentenbits legt den Wertebereich der darstellbaren Zahlen fest (s.u.). Dabei werden die Exponentenbits nicht direkt dargestellt, sondern auf einen Mittelwert (Bias) aufaddiert, der die Null
definiert. Dadurch können auch negative Exponenten einbezogen und dargestellt werden,
ohne auf die Darstellung durchs 2-Komplement zurückgreifen zu müssen.
Typ
single
Gesamtzahl der 1+r+p 32
bit
Bits
4 Bytes
Anzahl der Bits
p
23 bit
für die reduzierte
Mantisse M
Binärstellen der
1+p 24 bit
Mantisse m
Binärstellen
r
8 bit
des Exponent
Charakteristik
1 ≤ E ≤ 254
Bias
127
single ext.
> 42 bit
double ext
bit > 78 bit
> 30 bit
double
64
8 Bytes
52 bit
> 31 bit
53 bit
> 63 bit
> 10 bit
11 bit
> 14 bit
> 62 bit
1 ≤ E ≤ 2046
1023
Die prinzipielle Anordnung der Bits einer single floating point number zeigt die nachfolgende Abbildung. In einer Implementierung darf die konkrete Anordnung der Bits im Speicher von der
im Bild dargestellten abweichen und hängt insbesondere von der jeweiligen Bytereihenfolge
(litte/big endian) und ggf. weiteren Rechnereigenheiten ab.
Abbildung 1 Die Anordnung der Bits von Vorzeichen, Exponent und Mantisse bei IEEE 754
Die Anordnung mit Vorzeichen – Exponent – Mantisse in genau dieser Reihenfolge bringt die
dargestellten Gleitkommawerte in dieselbe Reihenfolge wie die durch dasselbe Bitmuster darstellbaren Signed-Integer-Werte (Beispiel?). Damit können für die Vergleiche von Gleitkommazahlen dieselben Operationen verwendet werden, wie für die Vergleiche von SignedIntegers. Kurz: die Gleitkommazahlen können lexikalisch sortiert werden.
Auch wenn wir hier hauptsächlich das Zahlenformat erörtern, liegt die Bedeutung der IEEE
754-Norm insbesondere auch darin, dass für Gleitkommazahlen genaue Vorschriften für
Rundung, arithmetische Operationen, Wurzelberechnung, Konversionen und Ausnahmebehandlung (engl. exception handling) festgelegt werden.
Sind im Exponenten einer Zahl alle Bits gesetzt (= 1) oder alle gelöscht (=0), so hat diese
Fließkommazahl eine besonderte Bedeutung gemäß folgender Tabelle
10
Exponent
111...111binär
111...111binär
000...000binär
000...000binär
000...001binär bis 111...110binär
Mantisse
000...000binär
≠ 000...000binär
000...000binär
≠000...000binär
beliebig
Bedeutung
+/– Unendlich
"Keine Zahl" (NaN = Not a Number)
+/– 0. (Null)
Denormalisierte Fließkommazahl
Normalisierte Fließkommazahl
Tabelle 2: Bedeutung besonderer Kodewörter in IEEE 754
"+/-Unendlich": Repräsentiert Zahlen, deren Betrag zu groß ist, um dargestellt zu werden.
Es wird zwischen +"Unendlich" und –"Unendlich" unterschieden. Die Berechnung von
+1.0/0.0 ergibt per Definition ebenfalls +"Unendlich".
"Keine Zahl" (NaN) : Damit werden ungültige (oder nicht definierte) Ergebnisse dargestellt,
z. B. wenn versucht wurde, die Quadratwurzel aus einer negativen Zahl zu berechnen. Einige
"unbestimmte Ausdrücke" haben als Ergebnis "keine Zahl", zum Beispiel 0.0/0.0 oder "Unendlich". Außerdem werden NaNs in verschiedenen Anwendungsbereichen benutzt, um
"Kein Wert" oder "Unbekannter Wert" darzustellen. Insbesondere der Wert mit dem Bitmuster 111...111 wird oft für eine "nicht initialisierte Fließkommazahl" benutzt.
IEEE 754 fordert zwei Nichtzahlen: stille NaN (NaNq – quiet) und Signal-NaN (NaNs – signal). Beide stellen explizit keine Zahlen dar. Eine Signal-NaN löst im Gegensatz zu einer stillen
NaN eine Ausnahme (Exception) aus, wenn sie als Operand einer arithmetischen Operation
auftritt. IEEE 754 ermöglicht dem Anwender das Einstellen von Traps (Fallen) bei Ausnahmebedingungen. Nutzt der Anwender diese Möglichkeit nicht, so wird im Allgemeinen statt
einer Signal-NaN eine stille NaN erzeugt.
Mit Signal-NaN kann man je nach Rechenanlage uninitialisierten Rechnerspeicher füllen, so
dass jedes Verwenden einer uninitialisierten Variablen automatisch eine Ausnahme auslöst.
Stille NaN ermöglichen den Umgang mit Rechnungen, die kein Ergebnis erzeugen können,
wie die Division 0/0. NaN erlauben auch das Abspeichern zusätzlicher Hilfsinformation, z.B.
über die Ursache der NaN. Damit wird die Diagnose der Fehlerursache während der Ausnahmebehandlung ermöglicht.
+/- Null: repräsentiert die absolute Null. Auch Zahlen, deren Betrag zu klein ist, um dargestellt zu werden (Unterlauf) werden auf Null gerundet. Ihr Vorzeichen bleibt dabei erhalten.
Negative kleine Zahlen werden so zu –0.0 gerundet, positive Zahlen zu +0.0. Beim direkten
Vergleich werden jedoch +0.0 und –0.0 als gleich angesehen. (Dieses ist übrigens der Grund
dafür, warum auf Gleitkommazahlen streng genommen keine Ordnung definiert ist, sie nicht
ordinal sind)
Denormalisierte Zahl: Ist das der Betrag des Ergebnisses (oder des Zwischenergebnisses)
einer Rechnung kleiner als die kleinste darstellbare Zahl der verwendeten endlichen Arithmetik, so wird es im Allgemeinen auf Null gerundet; das nennt man Unterlauf der Gleitkommaarithmetik, (engl. Underflow). Da dabei Information verloren geht, versucht man, Unterlauf
nach Möglichkeit zu vermeiden. Ist eine Zahl zu klein, um in normalisierter Form mit dem
kleinsten, von Null verschiedenen Exponenten gespeichert zu werden, so werden sie deshalb
nicht auf Null abgebildet, sondern als "Denormalisierte Zahl" mit normierter Mantisse gespeichert. Ihre Interpretation ist nicht mehr ± 1, mantisse ⋅ 2 exp onent sondern ± 0, mantisse ⋅ 2 de .
Dabei ist de eins weniger als der Wert des kleinsten "normalen" Exponenten, also null. Damit
lässt sich die Lücke zwischen der kleinsten normalisierten Zahl und Null verkleinern und einen
direkten Unterlauf verhindern.
11
Stattdessen bewirken die denormalisierten Zahlen in IEEE 754 einen „allmählichen“ Unterlauf, indem sehr kleine Werter "um die 0 herum" 224 (für 'single') bzw. 253 (für 'double') Werte
verwendet werden ohne einen Unterlauf zu bewirken. Da bei fester Zahl von Mantissenbits
bei abnehmender Zahl die Anzahl nutzbarerer Bits abnehmen, nimmt dabei auch die Genauigkeit mit kleiner werdendem Zahlenwert in dem Bereich der denormalisierten Zahlen ab.
Normalisierte Zahl: In allen anderen Fällen (den „Normalfällen“) berechnet sich der Wert v
der Zahl als v = (−1) s ⋅ (1, m0 m1 m2 ...) ⋅ 2 e0e1e2 ...−b . Hierbei ist s das Vorzeichenbit, mi sind die
Bits der Mantisse und ej die Bits des Exponenten. Der Wert b ist die Abweichung (engl.: bias), die
aus der Tabelle oben entnommen werden kann.
Als darstellbare Zahlenbereiche ergeben sich:
single precision:
double precision:
1.3.2
±1,18·10-38 ... ±3,40·10+38
±2,23·10-308 ... ±1,80·10+308
Umwandlung zu IEEE 754
Für die Umwandlung einer Dezimalzahl in die IEEE 754-Maschinendarstellung geht man wie
folgt vor10.
Als Beispiel soll 166,125 umgewandelt werden: 166,125dezimal = 166dezimal + 1/8dezimal =
10100110binär + 0,001binär = 10100110,001binär. Vor- und Nachkommastellen wurden also getrennt für sich umgewandelt. Nach Verschiebung des Kommas (Normalisieren) ist dies
10100110,001binär = 1,0100110001binär * 27.
•
Die Mantisse erhält man, indem man die 1 und das Komma weglässt: m =
0100110001. Um auf 23 Bit zu kommen wird (falls nötig) das Ende mit Nullen aufgefüllt: 0100110 00100000 00000000. Das Vorzeichen ist hier "0", da es sich um eine
positive Zahl handelt.
•
Für den Exponenten erhalten wir 7 (Anzahl Stellen, um die das Komma unter 2.
nach links verschoben wurde) und addieren 127 (Bias-Wert, da Darstellungsgenauigkeit vom Typ "Single"), so dass sich 134dezimal = 10000110binär ergibt. Damit ist das
Gesamtergebnis als Maschinenzahl: 10000110 0100110 00100000 00000000binär
= 43 26 20 00hex
Wichtig ist, dass Fließkommazahlen nur begrenzte Genauigkeit bieten und es zwangsweise zu
Rundungsfehlern kommt. Dies kommt vor allen dann zum Tragen, wenn man viele einzelne
Fließkommazahlen aufsummiert. Oft addieren sich hier die Rundungsfehler. Die Zahl 0,1dezimal
z.B. ist 0,000110011001100110011...binär, also eine Zahl mit periodischen, und somit unendlich
langen Nachkommastellen.
1.3.3
Genauigkeitsprobleme mit Gleitpunktzahlen
Für die meisten aller technisch- wissenschaftlichen Berechnungen reicht die Genauigkeit (oft
schon single, sonst aber double) aus. Trotzdem sollten jedem Programmierer spezifische Eigenarten der Gleitkommakodierung gegenwärtig sein, denn in manchen Fällen, kann dieses zu
überraschenden Ergebnissen führen. Durch die binäre Darstellung der Zahlen kommt es zu
„Gleitkommaartefakten“. Dies bedeutet dass Zahlen, die im Dezimalsystem präzise darstellbar
sind, z. B. als 12.45, als single precision-Gleitpunktzahl einen Wert von
10
Ein applet dazu ist auf http://www.h-schmidt.net/FloatApplet/IEEE754de.html zu finden.
12
12.44999999900468785 haben. Dies kann in nachfolgenden Berechnungen zu unvorhergesehenen Rundungsfehlern führen. Warum?
In einer Gleitpunktdarstellung wird eine reelle Zahl x ≠ 0 dargestellt durch ein a und ein e, so
dass gilt a = ∑ z i ⋅ b −i und x = a ⋅ b e mit e ∈ [emin , emax ] . Dadurch ergibt sich ein Rundungsp
i =1
{
fehler: Die obige Darstellung realisiert eine Projektion fl : R → x ∈ R | ∃a, e : x = ab e
und hat einen relativen Rundungsfehler
x − fl(x)
x
≤ε=
}
1 1− p
b mit p als die Anzahl der Bits der
2
Nachkommastellen. Für b = 2 ergibt sich ε = 2− p . Das heißt, ein ganzes Intervall auf dem
Zahlenstrahl, nämlich ( fl ( x) − ε , fl ( x) + ε ] , wird durch eine einzige Zahl fl(x) repräsentiert.
Bei single-Werten entspricht ε = 2− 23 ( ≈ 1,2 ⋅10 −7 )11, also sind single precision-Werte bei einer einfachen Zuweisung mindestens 6 Dezimalstellen genau. Bei double-Werten ist ε = 2− 52
( ≈ 2,2 ⋅ 10 −16 ), also sind bei einer einfachen Zuweisung mindestens 15 Dezimalstellen genau.
Dieser Rundungsfehler kann sich in komplizierten, häufig wiederholten Rechnungen allerdings
akkumulieren, und deshalb ist grundsätzlich Vorsicht geboten. Beachten Sie, dass dieser Rundungsfehler auf die absolute Größe der darzustellenden Zahl normiert ist, bei kleinen noch
darstellbaren Zahlen ist er absolut kleiner als bei großen Zahlen. Dieses bringt Probleme und
Merkwürdigkeiten mit sich, wenn kleine und große Zahlen addiert werden.
Insbesondere liegen Gleitkommazahlen nicht gleich dicht (im gleichen absoluten Abstand) auf
dem Zahlenstrahl: Bei single precision liegen z.B. zwischen 1.0 und 2.0 genau 8.388.607 (23 Bits1) verschiedene Gleitkommazahlen, zwischen 8192.0 und 8191.0 dagegen nur 2047 (12 Bits-1)
Zahlen, weil eben nicht die absolute Genauigkeit konstant ist, sondern die relative, also die
Anzahl der signifikanten Stellen.
Wie gesagt, in der Praxis stören diese Rundungsfehler oft nicht, aber Aufmerksamkeit ist bei
der Nutzung von Floating-Point Zahlen immer anzuraten. Weitere Programmierempfehlungen und -regeln betrachten wir in der Übung. In einem Aufsatz beschreibt Bruce Bush aus
praktischer Sicht die „Perils (Risiken, Gefahren) of Floating Point“: Unser neues Reading!
1.4 Alternative Dezimalzahlendarstellungen
Viele Programmiersprachen bieten außerdem alternative Datentypen an z.B. für Anwendungen die mit Geldbeträgen arbeiten, da insbesondere bei größeren Beträgen die Gleitkommafehler nicht hingenommen werden können. Es ist ja „undenkbar“, dass ab 1.000.000 € nicht
mehr auf den Cent genau gerechnet werden kann, siehe Beispiel oben! (Beachte, auch mit sehr
vielen kleinen „Rundungsfehlern“ kann man ggf. viel Geld „verdienen“ oder „verlieren“!)
Es sind drei Hauptformen solcher Darstellungen zu unterscheiden:
1.4.1
BCD-Code
Festkommazahlen sind Zahlenrepräsentationen, bei denen an einer bestimmten Binärstelle
einer Integer Zahl fest ein Dezimalkomma verabredet wird. Eine Festkommazahl besteht also
aus m Vorkomma- und n Nachkommastellen, wobei m+n der Länge des Maschinenwortes
11
zur Umbrechnung: log2 10 = ld 10 = 3.322
13
entspricht. Die höchstwertige Nachkommastelle entspricht dann dem Wert 2 −1 = 0,5 , die
nächste Stelle 2 −2 = 0,25 , usw. Um auf diese Art auf 1/100tel genau zu rechnen braucht man
7 binäre Nachkommastellen. Für manche Anwendungen besteht das Problem, dass Rundungen nicht genau so ausfallen wie im Dezimalsystem. 0,7 kann zum Beispiel auch bei beliebig
vielen Nachkommastellen nicht exakt repräsentiert werden.
Zur Lösung dieses Problems hat man historisch den BCD-Code (von engl. Binary Coded Decimal), gelegentlich auch 8-4-2-1-Code genannt, entwickelt. Dies ist ein numerischer Code, der
jede Ziffer einer Dezimalzahl einzeln in 4 Bit dual codiert. Die Ziffernfolge 8-4-2-1 steht dabei
für die Wertigkeit der Stellen
Um eine Zahl als BCD-Zahl darzustellen, wird jede dezimale Ziffer (0 bis 9) durch jeweils 4
Bit, also ein Halbbyte (Nibble), im Dualsystem dargestellt (00002 bis 10012). Die übrigen sechs
Werte, die mit 4 Bit darstellbar sind (10102 bis 11112), stellen keine gültigen BCD-Zahlen dar
(sind Pseudotetraden). Sie werden in manchen Systemen zur Codierung von Vorzeichen,
Überträgen oder Kommata verwendet.
Zur Codierung von Zahlen mit mehr als einer Dezimalziffer werden die BCD-Darstellungen
der einzelnen Ziffern hintereinander gesetzt (zum Beispiel wird die Zahl 2687 als 0010 0110
1000 0111, beziehungsweise ohne trennende Leerzeichen als 0010011010000111 dargestellt).
Mit einem Byte (8 Bit) können also zwei Dezimalziffern dargestellt werden. Werden die 4 Bits
einer BCD-Zahl jeweils in den niederwertigen Bits codiert und die restlichen 4 Bits mit Nullen
aufgefüllt, so spricht man von einer ungepackten BCD-Zahl. Werden beide Hälften eines Bytes mit je einer BCD-Zahl belegt, so nennt man dies entsprechend eine gepackte BCD-Zahl.
Diese BCD-Zahlendarstellung hat entgegen der üblichen Darstellung im Dualsystem, in dem
der Wert der kompletten Zahl und nicht der Wert jeder einzelnen Dezimalstelle dual codiert
wird, den Vorteil, dass sie sich einfacher in das Dezimalsystem übertragen lässt. Der Nachteil
gegenüber der Dualcodierung liegt in dem höheren Platzbedarf und der aufwendigeren Arithmetik, die meist mit Software und nicht mit Hardware bewältigt werden muss.
Der BCD-Code wird deshalb nur noch wenig eingesetzt. Aber zum Beispiel in COBOLProgrammen ist er zur Darstellung „gepackter Zahlen“ (PACKED DECIMAL) noch gebräuchlich. Früher wurden BCD-Zahlen insbesondere in Software für das Bankengewerbe
verwendet, da dort keine Rundungsfehler auftreten durften und deshalb mit „unendlicher Genauigkeit“ gerechnet werden sollte.
1.4.2
IEEE 854
Wenn man die Vorteile der Gleitkommadarstellung (insbesondere den großen Zahlenbereich)
erhalten will, so kann man insbesondere die Basis des Exponenten auch auf 10 festlegen, so
dass man wenigsten kompatibel zum Dezimalsystem ist. Ein solches Vorgehen ist im IEEE
854 (ANSI/IEEE Standard 854-1987) definiert: Standarddarstellungen für basis-unabhängige
Gleitkommazahlen in Computern. Der IEEE 854 ist eine Verallgemeinerung von IEEE 754.
Es ist allerdings zu erwarten, dass dieser von dem im Standardisierungsverfahren befindlichen
IEEE 754r kurzfristig abgelöst wird.. IEEE 754r ist eine Revision des vor etwa 20 Jahren verabschiedeten Gleitkommastandards IEEE 754. Die Diskussion über die Revision begann im
Jahr 2001 und ist noch nicht abgeschlossen
Hauptziele sind u.a.
•
die Reduktion von Implementierungsalternativen
•
halbe und vierfache Genauigkeit (Formate für 16, 32 64 und 128 Bit)
14
•
die von der Finanzwirtschaft als notwendig erachteten Dezimalformate (wie IEEE
854)
Der Standard soll Formate und Methoden für Gleitkommaarithmetik definieren.
Dicht gepackte Dezimalformate (3 Ziffern in 10 Bit) sind geplant. Die Dezimalformate werden hauptsächlich von der Finanzwirtschaft gefordert. Hier prallen zwei gegensätzliche Standpunkte aufeinander. Auf der einen Seite werden die Speicher-, Rechenzeit- und KostenVorteile, sowie die gleichmäßigere Zahlenverteilung eines dualen Formates herausgestellt. Auf
der anderen Seite wird argumentiert, dass exakte Ergebnisse (meist sind Ergebnisse wie bei
Handrechnungen gemeint – aber ist das exakter?) nur mit Dezimalarithmetik möglich sind und
in Zeiten schneller Prozessoren und billiger Speicher die Nachteile nicht mehr ins Gewicht
fallen. Manche Experten gehen sogar so weit zu behaupten, dass duale Arithmetik in Zukunft
kaum noch eine Rolle spielen wird. Ein zugegeben polemisches Zitat zu diesem Thema
stammt vom „Gleitpunktaltmeister“ Prof Kahan: „Why is Decimal floating-point hardware a good
idea anyway? Because it can help our industry avoid Errors Designed Not To Be Found.”
Bis heute muss eine Dezimalarithmetik (z.B. Python decimal) in der Regel in Software realisiert
werden, was sie um Größenordnungen langsamer macht als die Floating-Point-Arithmetik, die
heute fast immer durch Hardware unterstützt wird.
2 Objekte in Python
Für ein besseres Verständnis, wie in der Programmiersprache Python die Zahlen repräsentiert
werden, müssen wir uns ansehen, wie Python grundsätzlich Programmdaten repräsentiert. Alle
Daten eines Python-Programmes basieren auf dem allgemeinen Konzept von Objekten. Objekte umfassen grundlegende Datentypen wie Zahlen, Zeichenketten, usw. Dieses Kapitel beschreibt das Objektmodell von Python.
Jedes Datum im Speicher ist ein Objekt (object). Jedes Objekt hat
1. eine Identität,
2. einen Typ und
3. einen Wert.
Viele Objekte haben zusätzlich
•
einen Namen.
Wenn man z.B. a = 42 schreibt, wird ein Ganzzahl-Objekt mit dem Wert 42 erzeugt. Man
kann die Identität eines Objektes als „Zeiger“ auf einen Platz im Hauptspeicher betrachten, als
Hausnummer oder Adresse seines Speicherplatzes. a ist dabei der Name dieses Platzes. Der
Typ eines Objektes beschreibt die interne Repräsentation des Objektes mit den Methoden und
Operationen, die es unterstützt. Wird ein Objekt eines bestimmten Typs erzeugt, so wird dieses Objekt manchmal als eine Instanz dieses Typs bezeichnet.
Nachdem ein Objekt erzeugt wurde, können dessen Identität und Typ nicht mehr verändert
werden. Falls dessen Wert verändert werden kann, so sagt man, das Objekt ist veränderlich
(mutable). Falls dessen Wert nicht verändert werden kann, so spricht man entsprechend von
einem unveränderlichen (immutable) Objekt.
Ein Objekt, das Verweise auf andere Objekte enthält, bezeichnet man als Container oder
Sammlung (zusammengesetzter Datentyp).
15
Auf den eingebauten Typen (Basisobjekten, Basistypen, built-in types) sind Operatoren definiert. Diese dienen zur Verknüpfung und zum Vergleich von Objekten. Alle Operatoren sind
auch als Methode aufrufbar. Eine Methode ist eine Funktion, die eine gewisse Operation auf
einem Objekt ausführt, sobald sie angestoßen wird.
Zusätzlich zum Wert, den sie repräsentieren, definieren einige Objekte eine Anzahl von Datenattributen und Methoden, um auf den Attributen zu arbeiten. Ein Attribut ist dabei eine
mit dem Objekt assoziierte Eigenschaft, genauer gesagt, ein dort abgelegter Wert: ein String
oder eine Zahl. In der folgenden Abbildung ist dies an einer komplexen Zahl gezeigt. Sie hat
zwei Attribute (Speicherzellen) mit den Namen real und imag.
a
real:
imag:
3.0
4.0
Abbildung 2 Eine komplexe Zahl 3+4i als Objekt
Attribute und Methoden können mit dem Punkt-Operator (.) bezeichnet und so als Teil des
Objekts angesprochen werden, wie im folgenden Beispiel gezeigt wird.
a = 3 + 4j
r = a.real
# Erzeuge eine komplexe Zahl.
# Hole den Realteil (ein Attribut von a)
2.1 Typing in Python
Python ist eine Sprache mit dynamischen Datentypen, in der Namen Werte von verschiedenen Typen während der Programmabarbeitung repräsentieren können. Tatsächlich sind die
Namen eines Programmes lediglich „Etiketten“ für verschiedene Mengen und Objekte. Der
Zuweisungsoperator „=“ erzeugt nur eine Zuordnung zwischen einem Namen und dem Objekt. Das unterscheidet sich beispielsweise von C, wo ein Name für einen nach Größe und Ort
fixen Speicherbereich steht, in den Ergebnisse abgelegt werden. Das dynamische Verhalten
Pythons kann in folgendem Beispiel bei der Variable spam beobachtet werden. Zuerst wird
ihr ein ganzzahliger Wert zugewiesen. Dann jedoch wird ihr der Wert 7.5 zugewiesen.
>>> spam = 5
>>> spam = 1.5*spam
>>> spam
7.5
Diese Anweisung wertet den Ausdruck aus und weist dem Namen spam ein neues Ergebnis
zu. Sobald dies passiert, geht die ursprüngliche Bindung von spam an die Ganzzahl 5 verloren (wobei das Objekt mit der Ganzzahl 5 sogleich zur „Speicherbereinigung“ freigegeben
werden könnte). Darüber hinaus kann das Ergebnis einer Zuweisung den Typ einer Variablen
ändern durch eine für Ganzzahlen, Gleitkommazahlen und Komplexe Zahlen definierte, implizite Typumwandlung (coercion). In diesem Fall wechselt der Typ von spam von einer Ganzzahl (integer) zu einer Gleitkommazahl (float), da 1.5 eine Gleitkommazahl ist.
Grundsätzlich ist Python stark (streng) typisiert um Programmierfehler frühzeitig zu entdecken: Passen zwei Objekte nicht zusammen, da sie unterschiedlichen Typs sind (etwa die Multiplikation einer Zahl mit einem String), muss entweder definiert sein, was dann passieren soll,
oder es gibt eine Fehlermeldung.
16
2.2 Bezeichner
Ein Bezeichner ist ein Name, der zur Identifikation von Objekten (Zahlen, Zeichenketten,
Variablen, Funktionen, Klassen, Modulen etc.) benutzt wird. Benutzerdefinierte Namen
(vom Programmierer vergebene Namen) müssen dabei folgenden Regeln gehorchen:
Sie beginnen mit einem Buchstaben oder Unterstrich (underscore) _, gefolgt von beliebig vielen
Buchstaben, Unterstrichen oder Ziffern. Sonderzeichen wie Leerzeichen, $, % und @ sind
in Bezeichnern nicht erlaubt.
2.2.1
Syntax
Formal notiert sieht die Syntax folgendermaßen aus:
identifier ::=
(letter|"_") (letter | digit | "_")*
letter ::= lowercase | uppercase
lowercase ::=
"a"..."z"
uppercase ::=
"A"..."Z"
digit ::=
"0"..."9"
Achtung: In der Syntaxbeschreibung von Python wird für „Bezeichner“ der Begriff „identifier“
benutzt. Bitte nicht mit „Identität (identity)“ verwechseln (siehe oben).
Groß-/Kleinschreibung bei Namen ist in Python immer relevant: SPAM, spam und Spam
sind alles verschiedene Namen.
Folgende Namen sind reserviert und als benutzerdefinierte Namen verboten (diese 29 Namen haben eine spezielle Bedeutung). In der Python idle wird dieses auch dadurch gekennzeichnet, dass diese Namen „orange“ angezeigt werden.
and
assert
break
class
continue
def
del
elif
else
except
exec
finally
for
from
global
if
import
in
is
lambda
not
or
pass
print
raise
return
try
while
yield
Soweit die „harte“ Syntax.
Bezeichner können nur benutzt werden, wenn sie definiert sind. Möchte man einen Bezeichner für eine Variable, Attribut oder Methode aus einem anderen Programmteil (Modul) verwenden, so muss man die entsprechenden Programmteile vorher mit dem Befehl
from <Modulbezeichner> import <Bezeichner>
„importieren“, also bekannt machen. Als Bezeichner ist auch der Stern * für „Alle Bezeichner
des Moduls“ möglich.
Beispiel:
Statt
a = 2**13
kann man auch schreiben
from math import pow
a = pow(2,13)
17
# power(a,b) ist a^b
2.2.2
Konventionen
Bezeichner, die mit Unterstrichen beginnen, haben oft eine besondere Bedeutung.
Solche die mit einem einzigen Unterstrich anfangen, etwa _egg, werden durch die Anweisung
from module import * nicht geladen.
Der nur aus einem Unterstrich bestehende Name _ wird im interaktiven Editor genutzt und
steht für das Ergebnis der letzten Auswertung.
Bezeichner, die mit doppelten Unterstrichen beginnen und enden, etwa __init__, sind für
besondere Methoden reserviert.
Solche mit doppelten Unterstrichen nur am Anfang, etwa __bar, werden zur Implementierung von privaten Klassenmitgliedern benutzt.
Vermeiden Sie es, ähnliche Bezeichner für andere Zwecke zu verwenden, auch wenn
es formal erlaubt ist!
Namen von eingebauten Funktionen und Ausnahmen (exceptions), etwa open oder Syntax
Error (wir werden noch viele kennen lernen) sind keine reservierten Bezeichner. Sie existieren
im Modul __builtin__ und sind immer vorhanden, werden aber nur dann benutzt, wenn sie in
einem lokalen oder globalen Geltungsbereich (in Ihrem Programm) nicht „verdeckt“ werden.
Details folgen später. In der Python idle wird dieses auch dadurch gekennzeichnet, dass diese
Namen „violett“ angezeigt werden.
Über diese Konventionen hinaus gibt es für jede Programmiersprache übliche „Styleguides“.
Diese einzuhalten sind von der Sprachdefinition her nicht bedeutsam, aber es hilft sehr, ein
fremdes Programm (oder ein Programm das sie vor Wochen geschrieben haben) zu verstehen:
„A Foolish Consistency is the Hobgoblin (Kobold) of Little Minds (Kleingeister)”, ja, aber es ist
trotzdem wichtig: Es gebt um Konsistenz! Konsistenz in einem Projekt ist wichtig (wird z.B.
in Firmen durch Styleguides auch oft verpflichtend gemacht) aber Konsistenz in einem Programm, einem Modul oder einer Funktion ist extrem wichtig.
Es kommt einigen von Ihnen vielleicht lächerlich oder nervig vor, dass wir auf Namen so großen Wert legen. Tatsächlich ist aber eine gute Benennung von Variablen, Funktionen, Methoden und Klassen das allerwichtigste Kriterium für das Verständnis und damit für einen guten
Programmierer (und ein gutes Design)! Besonders beim objekt-orientierten Programmieren ist
das fast noch wichtiger als beim prozeduralen Programmieren. Eine schlechte Benennung
kann einen Modul fast unbrauchbar machen. Und dabei sagte doch Goethe: „Name ist Schall
und Rauch“... (Marthens Garten)
Vielleicht müssen Sie Ihre Kreativität nicht gerade an dieser Stelle ausleben und können die
folgenden „Spielregeln“ akzeptieren: Nach „Python Style Guide” von Guido van Rossum,
siehe http://www.python.org/doc/essays/styleguide.html .
Wir unterscheiden verschiedene prinzipielle Namensschreibweisen. In der folgenden Tabelle
sind diese angegeben, ggf. mit einer Jargonbezeichnung und der üblichen Nutzung (Lassen Sie
sich nicht irritieren, wenn Sie jetzt noch nicht genau wissen, was Funktionen, Methoden, Klassen etc. sind, lesen Sie doch einfach das, was hier zu Variablennamen steht!)
18
Schreibweise (Form)
Nutzung (Bedeutung)
x (single lowercase letter)
"Kleine" Variablen: Laufvariablen, Variablen mit
sehr kurzer „Lebensdauer“, 1-3 Buchstaben
i,j,k,… für int's
a,b,c,x,y,z … für float's
X (single uppercase letter)
lowercase
Lokale "Große" Variablen:
Längere Lebensdauer, größere Bedeutung. Labeling
names! ("Sprechende Namen") benutzen,
z.B. determinante, Modulnamen
lower_case_with_underscores
(Hilfs-) Funktionsnamen
Lokale Variablen wie bei lowercase. z.B. mein_alter
UPPERCASE
für Elemente von Mengen (set, frozenset)
ROT, GRUEN, BLAU
UPPER_CASE_WITH_UNDERSCORES
für Elemente von Mengen (set, frozenset)
FARBE_ROT
CapitalizedWords (or CapWords) “Pascal cas- Klassennamen, Funktionsnamen
ing” (wurde mit der Programmiersprache
PASCAL populär)
mixedCase
Methodennamen
(der erste Buchstabe ist klein geschrieben!)
„Camel casing“
Capitalized_Words_With_Underscores
(ugly!) bitte nicht !
Überlegen Sie sich bei der Wahl eines Namens für eine Variable, was ein anderer Programmierer aus dem Namen erkennen kann, wenn er den Code zum ersten Mal sieht und nichts darüber weiß. Er sollte am besten die Bedeutung aus dem Namen ersehen können. Längere Namen sind meistens besser zum Verstehen als kurze (zu lange sind für den Leser des Codes
natürlich auch lästig). Zum Beispiel ist ParameterUnavailException viel besser verständlich als parmunavlex.
Ein sehr gutes Kriterium dafür, dass ein objekt-orientiertes Design Fehler hat, sind Namen:
wenn sie zu lang werden, wenn sie keinen Sinn mehr machen von einem globalen Blickpunkt
aus oder wenn alle Funktionen doIt, make und thing heißen, dann ist es höchste Zeit, das
Design zu überprüfen! Wenn Klassennamen aus mehr als 3 Wörtern bestehen, dann ist das ein
Indiz dafür, dass der Programmierer verschiedene Entities seines Systems durcheinander bringt.
Noch eine kleine Anmerkung zu foo oder bar12, im Python Kontext oft spam oder eggs. Dieses
sind sogenannte metasyntaktische Variable13, die ausschließlich zur Benennung von beliebi-
12
Fubar ist ein Wort aus dem anglo-amerikanischen Sprachraum, dessen Herkunft nicht genau bestimmt
werden kann. Eine häufige Erklärung des Wortes ist das Herleiten aus der Zeit des Zweiten Weltkriegs. Dort
wurde Fubar von der US-Armee als Akronym für Fucked Up Beyond All Repair verwendet (englisch, zu
deutsch etwa Am Arsch und nicht zu reparieren). Die Bezeichnung Fubar lässt sich ggf. auch aus dem deutschen
Wort "furchtbar" ableiten.
13
Sie treten häufig in Serien auf und sind kulturspezifisch, so z.B.
foo, bar in allen möglichen Programmierbeispielen
spam, eggs vor allem in Python Beispielen
alice, bob, eve überwiegend bei kryptographischen Themen
bla(h), blubb, blablubb, muh, maeh, kiki, laberfasel im deutschsprachigen Raum
toto, titi, tata, tutu in Frankreich
pippo, pluto, paperino in Italien
19
gen Entitäten in Beispielen dient. Eine metasyntaktische Variable hat ansonsten keine Bedeutung. Man benutzt eine metasyntaktische Variable in der Regel nicht in einem echten, fertigen
Programm, wenn man stattdessen einen sinnvollen Namen verwenden kann.
3 Elementare numerische Datentypen in
Python
3.1 Numerische Datentypen
Python implementiert vier verschiedene numerische Basis-Typen:
•
Ganzzahlen (int - integer, in C longs, mindestens 32 bit)
•
lange Ganzzahlen (long - long integers, beliebig große Ganzzahlen),
•
Gleitkommazahlen (float - floating point, in C double)
•
komplexe Zahlen (complex)
und seit Version 2.4 (März 2005) auch einen speziellen Typ
•
Dezimalzahlen (decimal)
Alle numerischen Typen sind vorzeichenbehaftet und unveränderlich.
Ganzzahlen (integer) repräsentieren ganze Zahlen in mindestens 32 Bit Darstellung, also
Zahlen mindestens im Intervall zwischen -2.147.483.648 und 2.147.483.647 (größere Darstellungen sind Maschinenabhängig zugelassen). Intern werden ganze Zahlen als Binärzahlen im
Zweierkomplement mit 32 oder mehr Bits dargestellt. Falls das Ergebnis einer Operation außerhalb des erlaubten Wertebereichs liegt, wird automatisch in eine Lange Ganzzahl gewandelt.
Lange Ganzzahlen (long integers) repräsentieren ganze Zahlen unbegrenzter Größe (d.h.
praktisch begrenzt nur durch den verfügbaren Hauptspeicher).
Gleitkommazahlen (floating point) werden mit Hilfe der rechnerinternen doppelten Genauigkeit (64 Bit) repräsentiert. Normalerweise folgt diese dem Standard IEEE 754, der ungefähr
17 Stellen Genauigkeit bietet sowie einen Exponenten zwischen -308 und 308. Python unterstützt keine 32-Bit-Gleitkommazahlen einfacher Genauigkeit.
Komplexe Zahlen (complex) werden als Paar von Fließkommazahlen repräsentiert. Auf den
Real- und Imaginärteil einer komplexen Zahl z kann jeweils mit z.real und z.imag
zugegriffen werden.
Dezimalzahlen (decimal) sind nur verfügbar, wenn das Modul decimal bekannt gemacht
wurde, durch
import decimal
Sie sind für Anwendungen gedacht, in denen die prinzipiellen Ungenauigkeiten der
Gleitkommazahlen ins Gewicht fallen, z.B. für kommerzielle Anwendungen. Durch ein
20
sogenanntes Context-Objekt können die Genauigkeit (Anzahl der Dezimalstellen) und
die Rundungsarten ("round-down", "round-half-up", "round-half-even", "round-ceiling",
"round-floor", "round-half-down", and "round-up") sowie die Bedingungen für die Generierung von Ausnahmen (exceptions) festgelegt werden. Dieser Datentyp kann sowohl
Ganzzahlen, Festpunktzahlen als auch Dezimal-Gleitkommazahlen repräsentieren.
Dezimalzahlen dürfen beliebig groß sein (wiederum praktisch nur begrenzt durch den
verfügbaren Hauptspeicher), aber eine Genauigkeit von 20 Stellen wäre wohl gut genug
um das Bruttosozialprodukt der gesamten Welt auf den Cent genau zu repräsentieren.
Die aktuelle Implementierung basiert auf dem arithmetischen Modell des IEEE 854,
ANSI X3-274, und dem Vorschlag IEEE 754r.
Literale sind direkt in der Programmiersprache angegebene Werte für Operatoren. Python
erkennt an der Schreibweise, welcher Datentyp genutzt werden soll:
1234
-24
0
# integers
sind „normale“ Ganzzahlen in Dezimalschreibweise.
Man kann diese auch als Oktalzahl oder Hexadezimalzahl angeben:
0123
-0x12F
0X7FFFFFFF
# führende 0 (NULL) steht für Oktalzahl
# führendes 0x steht für Hexzahl oder
# führendes 0X gleichermassen
Lange Ganzzahlen:
2147483648
-999L
42l
# „implizite“long integer
# erzwungene long integer
Gleitkommazahlen
1.23
2.09e6
-4E200
4.0e-210
1.
.1
# 2.09⋅106
# es ist beides erlaubt, e und E
Beachten Sie: Die Schreibweise 2.09e6 repräsentiert den Wert 2,09 ⋅106 = 2.090.000 (e
oder E steht für Exponent zur Basis 10), kommt also der üblichen wissenschaftlichtechnischen Schreibweise nahe.
Komplexe Zahlen werden ähnlich wie in der Mathematik üblich notiert:
3+4j
3.0+4.0j
0-3J
1E3+1E1j
-3J
# auch hier ist j und J erlaubt
Sie werden durch zwei Gleitkommazahlen für Realteil und Imginärteil (Zusatz j oder J, nicht i
) repräsentiert. Alle Schreibweisen für Gleitkommazahlen sind erlaubt.
21
Dezimalzahlen können nur dann genutzt werden, wenn der Modul decimal importiert
wurde.
import decimal
decimal.Decimal ('35.71')
Decimal("35.71")
decimal.Decimal (35)
Decimal("35")
decimal.Decimal (35.71)
# Schreibweise als string
# ganze Zahlen dürfen auch
# angegeben werden
# erzeugt allerdings einen
# Fehler
Traceback (most recent call last):
File "<pyshell#4>", line 1, in -topleveldecimal.Decimal (35.71)
File "C:\Python24\lib\decimal.py", line 534, in __new__
raise TypeError("Cannot convert float to Decimal. " +
TypeError: Cannot convert float to Decimal. First convert the
float to a string
Hier gibt es einiges zu besprechen: Mit der ersten Anweisung machen wir in unserem Programm den Modul decimal bekannt. Jetzt können durch die um den Modulbezeichner erweiterten, sogenannten qualifizierten Namen in Punktnotation die Funktionen dieses Moduls genutzt werden.
Die zweite Zeile ruft die Methode Decimal im Modul decimal auf, die bewirkt, dass ein
Objekt vom Typ Decimal erzeugt wird (wir nennen sie Konstruktor). Der Wert dieses Objektes wird als Zeichenkette (string, siehe unten) mit dem im Englisch üblichen Dezimalpunkt
angegeben. Übrigens: decimal.decimal (beides klein geschrieben) funktioniert nicht, weil
die Funktion decimal nicht existiert. Auch erlaubt ist die Angabe einer Ganzzahl, wie in der
dritten Anweisung.
Nicht erlaubt ist die Angabe in Float-Notation (z.B. 35.71 wie in Zeile 4), weil keine Konvertierungsmethode von float nach Decimal zur Verfügung steht. Hier schlägt die Strenge
des Python Typsystems zu, wie die Fehlermeldung des Interpreters aussagt. Wurde diese
Funktion versehentlich vergessen? - Nein, es ist voll beabsichtigt, aber diskutierbar (siehe auch
http://www.python.org/peps/pep-0327.html#why-floating-point). Der Hauptgrund ist folgender:: Der Wert des Dezimalobjekt wird erzeugt, indem das angegebene Argument vom
Quelltyp (string, int, long, tupel (kommt später)) gewandelt wird. Würden wir float an
dieser Stelle zulassen, also eine Schreibweise 35.71, was ja als float interpretiert wird, so
würde die potentielle Ungenauigkeit der float-Repräsentation in den Dezimaltyp übertragen,
und dieses kann ggf. zu sehr überraschenden Ergebnissen führen; übrigens: in JAVA gibt es
eine solche Funktion (genauer: einen solchen Konstruktor), aber die Erfahrungen zeigen: “The
results of this constructor can be somewhat unpredictable and its use is generally not recommended.” (aus JAVAdocs). In Python hat man folgenden Grundsatz befolgt: „The first release
of the module should focus on that which is safe, minimal, and essential.”
Die Schreibweisen decimal.Decimal (spam) erscheint schon sehr umständlich, oder? Würde nicht Decimal (spam) ausreichen? - Tatsächlich kann man sich das Schreiben erleichtern,
aber dieses hat andere Nachteile in einem echten Programm. Die mögliche Erleichterung im
Interpreter, z.B. zum Ausprobieren ist folgende:
from decimal import *
Decimal ('35.71')
# Schreibweise als string
22
Decimal (35)
# Schreibweise als int
In dem Context-Objekt des Moduls decimal kann man verschiedene Einstellungen vornehmen, was aber an dieser Stelle sicherlich zu weit führen würde. Wir behandeln das später,
wenn wir die Konstrukte Modul, Objekt, etc. genauer betrachten. Zunächst seien nur die Default-Einstellungen (das sind jene, die nach dem Aufruf import gelten) angegeben. Als Beispiel sei der folgende Dialog im Interpreter kurz betrachtet:
>>> from decimal import *
Decimal (1)/Decimal(7)
Decimal("0.1428571428571428571428571429")
>>> decimal.getcontext ()
Context(prec=28, rounding=ROUND_HALF_EVEN, Emin=-999999999,
Emax=999999999, capitals=1, flags=[Inexact, Rounded],
traps=[InvalidOperation, DivisionByZero, Overflow])
Diese Meldung bedeutet ausführlich:
•
precision: is set to 28 (also 28 gültige Dezimalstellen)
•
rounding: is set to ROUND_HALF_EVEN (also: wenn die abzuschneidenden Ziffern größer als 0,5 sind, dann wird aufgerundet (+1 bei der letzten gültigen Ziffer);
wenn sie kleiner als 0,5 sind wird abgerundet (keine Veränderung); wenn sie = 0,5 ist,
wird so geändert, dass die letzte gültige Ziffer eine gerade Ziffer ist (!?!). Beispiele:
1.123
1.128
1.125
1.135
-->
-->
-->
-->
1.12
1.13
1.12
1.14
•
Emin: Kleinster erlaubter Exponent ist -999999999
•
Emax: Größter erlaubter Exponent ist 999999999
•
sowie weitere spezielle Settings insbesondere zur Fehlerbehandlung!
Mit dem Modul Decimal haben wir eine Möglichkeit kennen gelernt, wie die Funktionalität
einer Programmiersprache systematisch erweitert werden kann. Das werden wir noch häufig
nutzen. Aber zunächst einmal müssen wir uns die Operatoren anschauen, die zu den numerischen Basistypen gehören!
3.2 Operationen auf Zahlen
Die in den folgenden Tabellen angegebenen Operationen können auf allen numerischen Typen angewandt werden.
Operation
Beschreibung
Klassenmethode
x+y
Addition
__add__
x–y
Subtraktion
__sub__
x*y
Multiplikation
__mul__
x/y
Division
__div__
23
x ** y
Potenzieren (xy)
__pow__
x%y
Modulo (x mod y)
__mod__
–x
Einstellige Negation
__neg__
+x
Einstellige Identiät
__pos__
Grundsätzlich gilt, dass die Verknüpfung zweier Operanden nur dann erlaubt ist, wenn diese
den gleichen Typ haben. Das Ergebnis hat dann auch genau diesen Typ.
Zwangs-Typenconversion (Coercion): In Ausdrücken mit gemischten Typen konvertiert
Python numerische Operanden automatisch bis zum „höchsten“ verwendeten Typ, wobei gilt:
Integer < Long < Float < Complex. Arbeitet man mit decimal, so gilt Integer <
Long < Decimal. (Dieses gilt nicht bei der Verwendung der entsprechenden Klassenmethoden, hier müsste die Wandlung explizit durchgeführt werden!). „Zu große“ oder „zu kleine“ Integer-Ergebisse führen automatisch zu Longs (erzeugen keinen Überlauf (overflow)).
Bei Ganzzahlen ist das Ergebnis einer Division ebenfalls eine Ganzzahl. Daher wird 7/4 zu
1 ausgewertet und nicht zu 1.75. Dies soll sich allerdings in Python Version 3 ändern. Um
eine restlose Division „zukunftssicher“ zu formulieren, sollte man für Ganzzahldivision den
Operator „//“ benutzen.
Der Modulo-Operator % gibt den Rest der Division x/y zurück. Zum Beispiel ergibt
7%4 den Wert 3. Bei Fließkommazahlen gibt der Modulo-Operator den Rest von
x/y als eine Fließkommazahl zurück, die als x - int(x/y) * y berechnet wird. Bei
komplexen Zahlen ergibt der Modulo-Operator den Wert x - int((x/y).real) * y.
Folgende bitweise logische sowie Schiebe-Operatoren können nur auf Ganzzahlen und langen
Ganzzahlen angewendet werden:
Operation
Beschreibung
Klassenmethode
x << y
Schieben nach links um y Stellen
__lshift__
x >> y
Schieben nach rechts
__rshift__
x&y
Bitweises Und
__and__
x|y
Bitweises Oder
__or__
x^y
Bitweises Xor (exklusives Oder)
__xor__
~x
Bitweise Negation
__invert__
Die bitweisen Operatoren gehen davon aus, dass Ganzzahlen im binären 2er-Komplement
repräsentiert werden. Bei langen Ganzzahlen operieren die bitweisen Operatoren so, als ob das
Vorzeichenbit unendlich weit nach links hinaus geschoben wäre.
Zusätzlich können folgende eingebauten Funktionen auf alle numerischen Typen angewandt
werden:
24
Funktion
Beschreibung
Klassenmethode
abs(x)
Betrag
divmod(x, y)
Ergibt (int(x / y), x % y)
__abs__
pow(x, y [,modulo]) Ergibt (x ** y) % modulo
round(x [, n])
Rundet auf die nächste Ganzzahl (Ergebnis ist vom Typ float)
__divmod__
__pow__
__round__
Die Funktion abs() gibt den Betrag einer Zahl zurück. Die Funktion divmod() gibt den Quotienten und Rest einer Division zurück. Die pow()-Funktion kann anstelle des ** -Operators
verwendet werden, unterstützt aber auch die dreistellige Modulo-Exponentialfunktion, die oft
in kryptographischen Funktionen verwendet wird. Die Funktion round() rundet eine Fließkommazahl x auf das nächste Vielfache von 10 hoch n. Wird n weggelassen, so wird der Standardwert Null angenommen. Falls x von zwei Vielfachen gleich weit entfernt ist, so wird auf
den Wert gerundet, der weiter von Null entfernt ist (z.B. wird 0.5 auf 1 und -0.5 auf -1 gerundet).
>>> a=2.01e6
>>> a+.1
2010000.1000000001
>>> a-.1
2009999.8999999999
Anmerkungen zum Programmierstil, hier speziell Leerzeichen von Guido von Rossum:
Python Style Guide, siehe http://www.python.org/doc/essays/styleguide.html; aber das ist
wirklich Geschmackssache:
•
I hate whitespace in the following places: More than one space around an assignment
(or other) operator to align it with another, as in
x
y
long_name
= 1
= 2
= 3
Always write this as
x = 1
y = 2
long_name = 3
(Don't bother to argue with me on any of the above -- I've grown accustomed to this
style over 15 years.)
•
•
Always surround these binary operators with a single space on either side: assignment (
= ), comparisons ( == , < , > , != , <> , <= , >= , in , not in , is , is not), Booleans (
and , or , not ).
Use your better judgement for the insertion of spaces around arithmetic operators. Always be consistent about whitespace on either side of a binary operator. Some examples:
i = i+1
submitted = submitted + 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)
c = (a + b) * (a - b)
25
3.3 Weitere Operatoren und mathematische Funktionen
Neben dem Decimal-Modul stehen diverse andere Module zur Verfügung, wie z.B. das Modul
math, durch die man insbesondere alle trigonometrischen Funktionen und Konstanten wie π
und e zur Verfügung hat. Für wissenschaftliches Rechnen steht ein sehr leistungsfähiges Paket
scipy_core zur Verfügung. Dieses Paket unterstützt n-dimensionale Felder, nützliche Funktionen aus der Linearen Algebra, Fourrier Transformation, Zufallszahlen, usw.
3.4 Die streng objektorientierte Implementierung von Python
Python wendet im Gegensatz zu JAVA oder C++ das Klassenkonzept aus der Objektorientierten Programmierung auch auf die Basisdatentypen an. Alle eingebauten Datentypen bestehen aus irgendwelchen Daten und einer Ansammlung von besonderen Methoden. Die Namen dieser Methoden beginnen und enden immer mit zwei Unterstrichen (__). Diese Methoden werden automatisch vom Interpreter aufgerufen, während das Programm läuft. Zum Beispiel wird die Operation x + y auf eine interne Methode x.__add__(y) abgebildet. Das Verhalten jedes Datentyps hängt gänzlich von der Menge dieser speziellen Methoden ab, die es implementiert. Obwohl es nicht möglich ist, das Verhalten von eingebauten Typen zu verändern,
so ist es doch möglich, Klassendefinitionen zu benutzen, um neue Objekte zu definieren, die
sich wie die vorhandenen eingebauten Typen verhalten.
Datentyp
Objekt
Integer
Long Integer
Gleitkomma
Komplexe Zahlen
Dezimalzahlen
int
long
float
complex
ExtendedContext
BasicContext
DefaultContext
Man kann, aber wohl niemand wird es tun, diese internen Objekte nutzen; hier müssen die
Typen exakt stimmen, sonst wird ein Typfehler erzeugt. Jeder Komfort wird dabei dem Programmierer genommen.
>>> int.__add__(3,5)
8
>>> float.__add__(3.0,5.0)
8.0
>>> a=complex(3.0,1.0)
>>> b=complex(1.0,1.0)
>>> complex.__add__(a,b)
(4+2j)
>>> a.__add__(b)
(4+2j)
Zu jedem Python-Objekt gehört mindestens eine ganzzahlige Referenz (Adresse), eine Typbeschreibung sowie die Repräsentation der eigentlichen Daten. Tabelle 3 gibt den ungefähren
Speicherbedarf von verschiedenen eingebauten Objekten an, basierend auf einer Implementierung in C auf einem 32-Bit-Rechner. Die exakten Angaben können davon leicht abweichen,
abhängig von der Implementierung des Interpreters und der Rechnerarchitektur (z.B. kann
sich der Speicherbedarf auf einem 64-Bit-Rechner verdoppeln). Auch wenn Sie vielleicht nie
26
über Speicherausnutzung nachdenken: Python wird in einer Vielzahl von verschiedenen hochperformanten und speicherkritischen Anwendungen eingesetzt, von Supercomputern bis zu
mobilen Computern. Der Speicherbedarf der eingebauten Datentypen wird hier angegeben,
um es Programmierern zu erleichtern, die richtige Entscheidung bei speicherkritischen Einstellungen vorzunehmen.
Typ
Größe
Ganzzahl
12 Bytes
Lange Ganzzahl
12 Bytes + (nbits/16 + 1)*2 Bytes
Fließkommazahl
16 Bytes
Komplexe Zahl
24 Bytes
Liste
16 Bytes + 4 Bytes pro Element
Tabelle 3: Speicherbedarf von eingebauten Datentypen
4 Vergleiche und Boolesche Ausdrücke in
Python
Die in Abschnitt 3.2 angegebenen Operationen können zu längeren Ausdrücken (expressions)
kombiniert werden.
4.1 Ausdrücke
Folgende Tabelle führt die Auswertungsreihenfolge (Vorrangregeln) von Python-Operatoren
auf. Alle Operatoren außer der Exponentenbildung werden von links nach rechts ausgewertet
und sind in der Tabelle nach Priorität sortiert aufgeführt (oben höchste, unten geringste Priorität). Das heißt, Operatoren, die weiter oben in der Tabelle aufgeführt sind, werden vor solchen ausgewertet, die weiter unten aufgeführt sind. Man beachte, dass mehrere Operatoren,
die innerhalb von Unterausdrücken vorkommen, die gleiche Priorität haben, wie in x * y, x / y
und x % y von links nach rechts ausgewertet werden.
Diese ist die vollständige Tabelle. Wir haben bisher noch gar nicht alle Operatoren besprochen. Also bitte „ignorieren“ Sie fürs Erste die gelb unterlegten Zeilen, die behandeln wir später.
Operator
Name
(...), [...], {...} `...`
Tupel-, ListenKonversion
s[i], s[i:j] s.attr f(...)
Indizierung und Teilbereiche, Attribute, Funktionsaufruf
+x, -x, ~x
Einstellige Operatoren
x ** y
Potenzoperator (rechts-assoziativ)
x * y, x / y, x % y
Multiplikation, Division, Modulo
x + y,
Addition, Subtraktion
x-y
x << y, x >> y
und
Bitweises Schieben
27
Dictionary-Konversion,
String-
x&y
Bitweises Und
x^y
Bitweises Exklusives Oder
x |y
x < y, x <= y, x > y,
x >= y, x == y, x !=y,
x <> y, x is y, x is not
y, x in s, x not in s
not x
Bitweises Oder
Vergleichsoperatoren,
Identität, Tests auf Enthaltensein in Sequenzen
Logische Negation
x and y
Logisches Und
x or y
Logisches Oder
lambda args: expr
Anonyme Funktion
Wie in der Mathematik üblich, kann man die Auswertereihenfolge durch Klammerung ( … )
beeinflussen. Zugelassen sind allerdings nur runde Klammern. Diese können aber beliebig
geschachtelt werden. Hierzu einige Beispiele:
>>> 1+2*3+4
11
>>> (1+2)*(3+4)
21
>>> ((1+2)*3)+4
13
4.2 Vergleichsoperationen
Folgende Vergleichsoperatoren verfügen über die übliche mathematische Interpretation und
geben einen Typ Boolean (siehe unten) mit dem Wert ‚True’ oder ‚False’ zurück.
Operation
Beschreibung
Klassenmethoden
x<y
Kleiner als
__lt__
x>y
Größer als
__gt__
x == y
Gleich, Achtung:nicht einfach =
__eq__
x != y
Ungleich (auch: x <> y) aber != wird allgemein bevorzugt
__ne__
x >= y
Größer-gleich
__le__
x <= y
Kleiner-gleich
__ge__
Vergleiche können wie in w < x < y < z verkettet werden. Solche Ausdrücke werden ausgewertet als w < x and x < y and y < z. Ausdrücke der Form x < y > z sind erlaubt, verwirren
aber sehr wahrscheinlich alle anderen, die den Code lesen (es ist wichtig zu wissen, dass in einem solchen Ausdruck kein Vergleich zwischen x und z stattfindet).
Bei Vergleichen von komplexen Zahlen werden erst die Real- und dann die Imaginärteile verglichen. Daher ist 3 + 2j kleiner als 4 + 1000j und 2 + 1j kleiner als 2 + 4j.
28
4.3 Boolsche Ausdrücke
Wir haben in den obigen Diskussionen mehrfach die Begriffe „wahr“ und „falsch“ benutzt.
„Wahr“ ist jede von Null verschiedene Zahl sowie jedes nichtleere Sammlungsobjekt (Liste,
Dictionary, etc.) Die Namen True und False sind den Wahrheitswerten „wahr“ respektive
„falsch“ zugeordnet und verhalten sich fast immer wie die Integer 1 und 0. Der Typ bool ist
als Unterklasse von int implementiert und unterscheidet sich in der Ausgabe (print) von Instanzen: 0 wird als False ausgegeben, alle anderen Werte als True, also zum Beispiel nicht als 1.
Das spezielle Objekt None hat den Wahrheitswert False.
Boolesche Ausdrücke entstehen durch die Verwendung der Operationen
X or Y,
X and Y oder
not X
Alle built-in Typen in Python unterstützen Vergleiche und geben entweder True oder False
zurück. Sie werden bei zusammengesetzten Typen rekursiv angewandt, um ein Ergebnis zu
liefern. Vergleichsoperatoren sind:
X
X
X
X
X
X
X
X
X
X
< Y
<= Y
> Y
>= Y
== Y
!= Y
is Y
is not Y
in S
not in S
echt kleiner als
kleiner oder gleich
echt größer als
größer oder gleich
gleich (gleicher Wert)
ungleich (es geht auch <>, Verwendung nicht empfohlen)
gleiches Objekt (nur für veränderliche Objekte, z.B. Liste, relevant)
nicht gleiches Objekt
Test auf Enthaltensein in einem Sequenz-Objekt, z.B. String, Liste
Test auf Nicht-Enthaltensein in einem Sequenz-Objekt
Die Vergleichsoperatoren haben unter sich die gleiche Priorität, die kleiner ist als die aller numerischen Operatoren.
Vergleiche können verkettet werden. Beispielsweise prüft a < b == c , ob a kleiner ist als b
und außerdem, ob b gleich c ist.
Vergleiche können mit den Booleschen Operatoren and und or kombiniert werden; das Resultat eines Vergleichs (oder irgendeines anderen Booleschen Ausdrucks) kann mit not negiert
werden. All diese Operatoren haben wiederum niedrigere Priorität als Vergleichsoperatoren.
Unter ihnen hat not die höchste Priorität und or die geringste, so dass A and not B or C
äquivalent ist zu (A and (not B))or C. Natürlich können Klammern gesetzt werden, um die
gewünschte Zusammensetzung auszudrücken.
Die Booleschen Operatoren and und or sind so genannte Abkürzungs-Operatoren (engl.
shortcut): ihre Argumente werden von links nach rechts ausgewertet, und die Auswertung
wird beendet, sobald das Ergebnis feststeht. Wenn etwa A und C wahr sind, aber B ist falsch,
so wertet A and B and C den Ausdruck C nicht aus. Allgemein gilt, dass der Rückgabewert
eines Abkürzungs-Operators (wenn als allgemeiner Wert und nicht als Boolescher Ausdruck
verwendet) das zuletzt ausgewertete Argument ist.
Es ist möglich, das Resultat eines Vergleichs oder eines anderen Booleschen Ausdrucks einer
Variablen zuzuweisen, aber das ist „sehr tricky“, z.B.
29
>>> string1, string2, string3 = '', 'Uebung', 'macht'
>>> non_null = string1 or string2 or string3
>>> non_null
'Uebung'
Man beachte dass in Python (anders als in C) Zuweisungen in Ausdrücken nicht erlaubt sind.
Sequenz-Objekte dürfen mit anderen Objekten vom gleichen Sequenz-Typ verglichen werden.
Der Vergleich basiert auf lexikographischer Ordnung: zuerst werden die ersten beiden Elemente verglichen, und wenn sie sich unterscheiden, bestimmt dies bereits das Resultat. Wenn
sie gleich sind, werden die nächsten beiden Elemente verglichen, und so weiter, bis eine der
beiden Sequenzen erschöpft ist. Wenn zwei zu vergleichende Elemente selbst Sequenzen vom
gleichen Typ sind, wird der lexikographische Vergleich rekursiv fortgesetzt. Falls alle Elemente
einer Sequenz gleich sind, werden die Sequenzen als gleich betrachtet. Falls eine Sequenz eine
Anfangssequenz der anderen ist, ist die gekürzte Sequenz die kleinere. Die lexikographische
Ordnung für Strings verwendet die ASCII-Ordnung für einzelne Zeichen. Einige
Beispiele für Vergleiche von Sequenzen desselben Typs, die alle True ergeben:
>>> (1, 2, 3) < (1, 2, 4)
True
>>> [1, 2, 3] < [1, 2, 4]
True
>>> 'ABC' < 'C' < 'Pascal' < 'Python'
True
>>> (1, 2, 3, 4) < (1, 2, 4)
True
>>> (1, 2, 3) == (1.0, 2.0, 3.0)
True
>>> (1, 2, ('aa', 'ab')) < (1, 2, ('abc', 'a'), 4)
True
>>>
Man beachte, dass es erlaubt ist, Objekte verschiedenen Typs zu vergleichen. Das Resultat ist
deterministisch, aber beliebig: die Typen sind nach ihrem Namen geordnet. Daher ist eine
Liste immer kleiner als ein String, ein String immer kleiner als ein Tupel, etc. Gemischte numerische Typen werden nach ihrem numerischen Wert verglichen, d.h. 0 ist gleich 0.0, etc.
Die Python-Syntax (etwas vereinfacht) für boolesche Ausdrücke und Vergleiche lautet:
expression ::=
or_test ::=
and_test ::=
not_test ::=
lambda_form ::=
or_test | lambda_form
and_test | or_test "or" and_test
not_test | and_test "and" not_test
comparison | "not" not_test
"lambda" [parameter_list]: expression
comparison ::=
or_expr ( comp_operator or_expr )*
or_expr ::=
xor_expr ::=
and_expr ::=
shift_expr ::=
a_expr ::=
xor_expr | or_expr "|" xor_expr
and_expr | xor_expr "\textasciicircum" and_expr
shift_expr | and_expr "\;SPMamp;" shift_expr
a_expr | shift_expr ( "<<" | ">>" ) a_expr
m_expr | a_expr "+" m_expr
| a_expr "-" m_expr
u_expr | m_expr "*" u_expr
| m_expr "//" u_expr
| m_expr "/" u_expr
| m_expr "\%" u_expr
m_expr ::=
30
u_expr ::=
power | "-" u_expr | "+" u_expr | "\~" u_expr
comp_operator ::=
"<" | ">" | "==" | ">=" | "<=" | "<>" | "!="
| "is" ["not"] | ["not"] "in"
Man sieht, eine Syntax ist wirklich nicht immer einfach zu lesen – aber sie ist eben präzise!
Lambda List ist ein Konstrukt aus der funktionalen Programmierung und wird hier nicht
weiter, sondern in PRG 2 behandelt.
5 Zuweisungen von Objekten
5.1 Einfache Zuweisungen
Wenn ein Programm eine Zuweisung (assignment) wie in a = b vornimmt, wird eine neue
Referenz auf b erzeugt. Für einfache Objekte wie Zahlen und Strings erzeugt diese Zuweisung eine Kopie von b. Für veränderliche Objekte wie Listen und Dictionaries (diese lernen wir erst später kennen) ist dieses Verhalten jedoch gänzlich verschieden:
b = [1, 2, 3, 4]
a = b
a[2] = -100
print b
#
#
#
#
b ist eine Liste, veränderlich
a ist eine Referenz auf b.
Ändere ein Element in 'a'.
Ergibt '[1, 2, -100, 4]'.
Da a und b in diesem Beispiel dasselbe Objekt referenzieren, macht sich eine Änderung der
einen Variablen bei der anderen bemerkbar. Um dies zu vermeiden, muss man eine Kopie des
Objektes anfertigen und nicht nur eine neue Referenz darauf.
Es gibt zwei verschiedene Möglichkeiten, Objekte wie Listen und Dictionaries zu kopieren:
eine flache und eine tiefe Kopie. Eine flache Kopie erzeugt ein neues Objekt, füllt es jedoch
mit Referenzen auf die Elemente des ursprünglichen Objektes. Beispiel:
>>> b = [1, 2, [3, 4]]
>>> a = b[:]
>>> a[0] = 100
>>> print a
[100, 2, [3, 4]]
>>> print b
[1, 2, [3, 4]]
# geschachtelte Liste
# Erzeuge eine flache Kopie von b
# Verändere ein Element von a
# wirkt sich nicht in b aus.
Man sieht, dass die Veränderung eines Werts in der einen Liste nicht die Kopie beeinflusst.
>>> a[2][0] = -100
# Ändere Element von a eine Ebene tiefer
>>> print a
[100, 2, [-100, 4], 100]
>>> print b
# nun ist b auch beeinflusst!
[1, 2, [-100, 4]]
>>>
In diesem Fall sind a und b zwar eigenständige Listenobjekte, aber beide teilen sich Elemente
darin, die nur als Referenzen enthalten sind, so wie die enthaltene Liste. Daher werden bei
jeder Änderung dieser Elemente von a auch die entsprechenden Elemente in b verändert, wie
man sieht.
31
Dagegen erzeugt eine tiefe Kopie ein neues Objekt und kopiert rekursiv alle darin befindlichen Objekte. Es gibt keine eingebaute Funktion, um tiefe Kopien von Objekten anzufertigen,
aber die Funktion copy.deepcopy() aus der Standardbibliothek kann dazu wie folgt benutzt werden:
import copy
b = [1, 2, [3, 4]]
a = copy.deepcopy(b)
# import ohne Angabe des Modulnamens
Zusammengefasst:
1.
2.
Zuweisungen speichern Referenzen auf Objekte in Zielen.
Ausdrücke geben Objekte zurück.
ziel = ausdruck
3.
Ziele können sein:
• einfache Namen
• qualifizierte Attribute (in Objekten)
• Indizes und Teilbereiche
Auch Mehrfachzuweisungen sind möglich: Diese Form weist jedem Ziel dasselbe Objekt
ausdruck zu.
ziel1 = ziel2 = ausdruck
Diese Form weist paarweise zu, von links nach rechts:
ziel1, ziel2 = ausdruck1, ausdruck2
5.2 Erweiterte Zuweisung
Python verfügt über einen Satz von sogenannten erweiterten Zuweisungen (augmented assignment). Diese Formen enthalten einen binären Operator und eine Zuweisung. Man nennt sie
auch in place Operatoren. Die folgenden beiden Formen sind fast äquivalent:
X = X + Y
X += Y
Der Vorteil der zweiten Schreibweise ist, dass die Referenz auf X nur einmal aufgelöst werden
muss. Insbesondere bei veränderlichen Objekten kann diese Form zur PerformanceOptimierung genutzt werden und sind deshalb zu bevorzugen.
Zusammengefasst sind folgende erweiterten Zuweisungen verfügbar:
X
X
X
X
X
X
+= Y
-= Y
*= Y
%= Y
/= Y
//= Y
#
#
#
#
#
#
Addition
Subtraktion
Multiplikation
Modulo (x mod y)
Ganzzahl Division
Ganzzahl Division
32
X
X
X
X
X
X
**= Y
&= Y
|= Y
^= Y
<<= Y
>>= Y
#
#
#
#
#
#
Potenzieren
Bitweise logische UND
Bitweise logisches ODER
Bitweise logisches Exkusives ODER (XOR)
Links schieben
Rechts schieben
Achtung : Bitte nicht verwechseln mit X != Y . Das ist der Vergleichsoperator „ungleich“
und liefert TRUE oder FALSE!
6 Reading
Das folgende Reading wird auch für die Übung benötigt: The Perils of Floating Point by Bruce M. Bush , Quelle: http://www.lahey.com/float.htm .
Die Code-Beispiele sind in FORTRAN. Ihre Aufgabe wird es sein, diese zu verstehen und in
Python zu übertragen.
33
Anhang 1 Charakterisierung von Zahlen
Der Hintergrund der Implementierung von Zahlen und ihren Eigenschaften bildet die Mathematik. Für ein besseres Verständnis sollen hier noch einmal kurz die wichtigsten Zahleneigenschaften referiert werden.
Die Menge der natürlichen Zahlen N enthält je nach Definition die positiven ganzen Zahlen,
also
N = {1,2,3,...}
oder die nichtnegativen ganzen Zahlen, also
N = {0,1,2,3,...}
Für diese beiden verschiedenen Konventionen gibt es sowohl historische als auch praktische
Gründe. Die Definition ohne die Null steht in der älteren Tradition, da die natürlichen Zahlen
ohne die Null lange Zeit die einzigen bekannten Zahlen waren.
Die Menge der ganzen Zahlen umfasst alle natürlichen Zahlen mit Null {0,1,2,3,...} , sowie
die Negativen {− 1,−2,−3,...} aller natürlichen Zahlen (-0 ist gleich 0, wird daher nicht separat
genannt).
Die ganzen Zahlen bilden einen Ring bezüglich der Addition und der Multiplikation, d. h. sie
können ohne Einschränkung addiert, subtrahiert und multipliziert werden. Dabei gelten Rechenregeln wie das Kommutativgesetz und das Assoziativgesetz für Addition und Multiplikation, außerdem gelten die Distributivgesetze.
Das neutrale Element der Addition ist 0, das additiv inverse Element von n ist −n, das neutrale Element der Multiplikation ist 1.
Die Menge der ganzen Zahlen ist total geordnet, in der Reihenfolge
{..., −3, −2, −1, 0, +1, +2, +3,...}
d.h. man kann je zwei ganze Zahlen vergleichen.
Eine rationale Zahl ist eine Zahl, die als Verhältnis (lateinisch Ratio) zweier ganzer Zahlen
a
ausgedrückt werden kann (für gewöhnlich schreibt man a/b oder
), wobei der Nenner (hier
b
b) ungleich Null ist. Jede Zahl, die sich als Bruch zweier ganzer Zahlen darstellen lässt, ist also
eine rationale Zahl. Die Menge aller rationalen Zahlen bezeichnen wir mit Q.
Die rationalen Zahlen haben neben der Darstellung als gemeiner Bruch eine andere Darstellung, nämlich die Dezimalbruchentwicklung; z. B. ist
1/3
9/7
= 0,333333...
= 1,285714 285714...
34
1/2
= 0,50000...
1 = 1/1 = 1,0000... = 0,9999...
Die Dezimalentwicklungen rationaler Zahlen sind stets periodisch oder endlich (d.h. periodisch mit Periode 0).
Rationale Zahlen liegen dicht auf der Zahlengeraden: jede reelle Zahl, d.h. jeder Punkt auf der
Zahlengerade, kann beliebig genau durch rationale Zahlen angenähert werden.
Zwischen zwei rationalen Zahlen a und b liegt stets eine weitere rationale Zahl c (und somit
beliebig viele). Man nehme einfach das arithmetische Mittel dieser beiden Zahlen:
c: = (a + b) / 2
Was zunächst überraschend klingt, ist die Tatsache, dass die Menge der rationalen Zahlen
„gleichmächtig“ zu der Menge der natürlichen Zahlen ist. Das heißt: es gibt eine bijektive Abbildung zwischen N und Q, die jeder rationalen Zahl q eine natürliche Zahl n zuweist und umgekehrt.
Reelle Zahlen sind eine Erweiterung der rationalen Zahlen um Zahlen, denen man sich mit
rationalen Zahlen beliebig annähern kann. Die Menge der reellen Zahlen steht anschaulich in
einer umkehrbar eindeutigen Beziehung (einer Bijektion) mit den Punkten auf der Zahlengeraden.
Die reellen Zahlen, die nicht rational sind, nennt man irrationale Zahlen. Zum Beispiel ist
2 eine irrationale Zahl, weil sie nicht rational ist, aber man sich ihr beliebig annähern kann,
zum Beispiel mit dem Heron-Verfahren (nach Heron von Alexandria)
x1 = 1,
1
2 
x n +1 =  x n + 
2
xn 
oder mit den endlichen Dezimalbrüchen
1, 1,4; 1,41; 1,414; 1,4142; 1,41421; 1,414213; 1,4142135; ...
Für die Menge der reellen Zahlen wird das Symbol R verwendet. Die reellen Zahlen und
Funktionen von R nach R sind der Untersuchungsgegenstand der reellen Analysis.
Der Bereich der reellen Zahlen besteht also aus den rationalen Zahlen (ganze Zahlen wie -1, 0,
1 und Bruchzahlen wie 3/4 und -2/3,...) und den irrationalen Zahlen (z. B. π und 2 ). Dabei
ist die Menge der rationalen Zahlen abzählbar (Cantorsches Diagonalverfahren) und die Menge der irrationalen Zahlen überabzählbar.
Die Menge der irrationalen Zahlen lässt sich weiter zerlegen in die Menge der algebraischen
reellen Zahlen (der reellen Lösungen algebraischer Gleichungen) und die Menge der transzendenten reellen Zahlen (der übrigen). Dabei ist jede rationale Zahl auch algebraisch. Auch
die Menge der algebraischen Zahlen ist immer noch abzählbar. Erst die Menge der transzendenten Zahlen ist überabzählbar. Die Menge der reellen Zahlen besteht - aus dieser Sicht betrachtet - also sozusagen "fast nur" aus transzendenten Zahlen.
Die Konstruktion der reellen Zahlen aus den rationalen Zahlen ist etwas mühselig. Eine weitere Möglichkeit, die reellen Zahlen zu erfassen, ist, sie axiomatisch einzuführen. Im Wesentli-
35
chen benötigt man dazu drei Gruppen von Axiomen - die Körperaxiome, die Axiome der
Ordnungsstruktur sowie ein Axiom, das die Vollständigkeit garantiert.
1. Die reellen Zahlen sind ein Körper
2. Die reellen Zahlen sind total geordnet (s.a. geordneter Körper), d.h. für alle reellen
Zahlen a, b, c gilt:
•
es gilt genau eine der Beziehungen a < b,a = b,b < a (Trichotomie)
•
aus a < b und b < c folgt a < c (Transitivität)
•
aus a < b folgt a + c < b + c (Verträglichkeit mit der Addition)
•
aus a < b und c > 0 folgt ac < bc (Verträglichkeit mit der Multiplikation)
Die reellen Zahlen sind ordnungsvollständig, d.h. jede nichtleere, nach oben beschränkte Teilmenge von R besitzt ein Supremum
Die komplexen Zahlen C erweitern den Zahlenbereich der reellen Zahlen derart, dass auch
Wurzeln negativer Zahlen berechnet werden können. Dies gelingt durch Einführung einer
neuen Zahl i als Lösung der Gleichung x 2 = −1 . Diese Zahl i wird auch als imaginäre Einheit
bezeichnet. Die Einführung der imaginären Einheit i als neue Zahl wird Leonhard Euler zugeschrieben.
Komplexe Zahlen werden meist in der Form a + b i dargestellt, wobei a und b reelle Zahlen
sind und i die imaginäre Einheit ist. Auf die so dargestellten komplexen Zahlen lassen sich die
üblichen Rechenregeln für reelle Zahlen anwenden, wobei i2 stets durch −1 ersetzt werden
kann und umgekehrt.
Der so konstruierte Zahlenbereich der komplexen Zahlen hat eine Reihe vorteilhafter Eigenschaften, die sich in vielen Bereichen der Natur- und Ingenieurswissenschaften als äußerst
nützlich erwiesen hat. Einer der Gründe für diese positiven Eigenschaften ist die algebraische
Abgeschlossenheit der komplexen Zahlen. Dies bedeutet, dass jede algebraische Gleichung
über den komplexen Zahlen eine Lösung besitzt. Diese Eigenschaft ist der Inhalt des Fundamentalsatzes der Algebra. Ein weiterer Grund ist ein Zusammenhang zwischen trigonometrischen Funktionen und der Exponentialfunktion, der über die komplexen Zahlen hergestellt
werden kann.
Quaternionen sind eine Verallgemeinerung der komplexen Zahlen. Erdacht wurden sie 1843
von Sir William Rowan Hamilton und werden gelegentlich auch Hamilton-Zahlen genannt.
Die Menge der Quaternionen wird meist mit H bezeichnet.
Jedes Quaternion ist durch vier reelle Komponenten x0, x1, x2, x3 eindeutig bestimmt. Oft werden Quaternionen als Linearkombination dieser vier Komponenten mit vier Basiselementen 1,
i, j, k dargestellt:
x0 + x1 i + x 2 j + x3 k
Mit Quaternionen lassen sich Rotationen um beliebige Achsen im Raum beschreiben. Genutzt
wird dies heutzutage im Bereich der Computergrafik sowie bei der Steuerung und Regelung
von Satelliten. Bei Verwendung von Quaternionen an Stelle von Rotationsmatrizen werden
etwas weniger Rechenoperationen benötigt. Insbesondere, wenn viele Rotationen miteinander
kombiniert (multipliziert) werden, steigt die Verarbeitungsgeschwindigkeit.
36
Herunterladen