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