Informatik für Nebenfächler



Ähnliche Dokumente
Zahlensysteme Seite -1- Zahlensysteme

Grundlagen der Informatik

1. Das dekadische Ziffernsystem (Dezimalsystem) Eine ganze Zahl z kann man als Summe von Potenzen zur Basis 10 darstellen:

Lineargleichungssysteme: Additions-/ Subtraktionsverfahren

BITte ein BIT. Vom Bit zum Binärsystem. A Bit Of Magic. 1. Welche Werte kann ein Bit annehmen? 2. Wie viele Zustände können Sie mit 2 Bit darstellen?

Eine Logikschaltung zur Addition zweier Zahlen

Einführung in die Programmierung

Primzahlen und RSA-Verschlüsselung

Daten verarbeiten. Binärzahlen

Zahlensysteme: Oktal- und Hexadezimalsystem

Einführung in die Informatik I

Zahlensysteme. Digitale Rechner speichern Daten im Dualsystem 435 dez = binär

Zahlensysteme. von Christian Bartl

Grundbegriffe der Informatik

Informationssysteme Gleitkommazahlen nach dem IEEE-Standard 754. Berechnung von Gleitkommazahlen aus Dezimalzahlen. HSLU T&A Informatik HS10

Technische Informatik - Eine Einführung

Programmiersprachen und Übersetzer

Ein polyadisches Zahlensystem mit der Basis B ist ein Zahlensystem, in dem eine Zahl x nach Potenzen von B zerlegt wird.

Der Aufruf von DM_in_Euro 1.40 sollte die Ausgabe 1.40 DM = Euro ergeben.

2.11 Kontextfreie Grammatiken und Parsebäume

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen

Im Original veränderbare Word-Dateien

Daten, Informationen, Kodierung. Binärkodierung

Grundlagen der Informatik Übungen 1.Termin

Wintersemester Maschinenbau und Kunststofftechnik. Informatik. Tobias Wolf Seite 1 von 18

2. Negative Dualzahlen darstellen

Mathematische Grundlagen der Kryptographie. 1. Ganze Zahlen 2. Kongruenzen und Restklassenringe. Stefan Brandstädter Jennifer Karstens

1 Mathematische Grundlagen

Binärdarstellung von Fliesskommazahlen

Theoretische Informatik SS 04 Übung 1

Programmierkurs Java

Zahlensysteme. Zahl Stellenwert Zahl Zahl =

2 Darstellung von Zahlen und Zeichen

Übung zur Vorlesung Einführung in die Computerlinguistik und Sprachtechnologie

Zahlendarstellungen und Rechnerarithmetik*

Zeichen bei Zahlen entschlüsseln

Jede Zahl muss dabei einzeln umgerechnet werden. Beginnen wir also ganz am Anfang mit der Zahl,192.

1. Man schreibe die folgenden Aussagen jeweils in einen normalen Satz um. Zum Beispiel kann man die Aussage:

Kapitel 2: Formale Sprachen Kontextfreie Sprachen. reguläre Grammatiken/Sprachen. kontextfreie Grammatiken/Sprachen

Englische Division. ... und allgemeine Hinweise

Grundlagen der Theoretischen Informatik, SoSe 2008

Binär- und Hexadezimal-Zahl Arithmetik.

Zahlensysteme Das 10er-System

Die Gleichung A x = a hat für A 0 die eindeutig bestimmte Lösung. Für A=0 und a 0 existiert keine Lösung.

Einführung in PHP. (mit Aufgaben)

Mathematik: Mag. Schmid Wolfgang Arbeitsblatt 3 1. Semester ARBEITSBLATT 3 RECHNEN MIT GANZEN ZAHLEN

Erwin Grüner

Sin-Funktion vgl. Cos-Funktion

4. Jeder Knoten hat höchstens zwei Kinder, ein linkes und ein rechtes.

3 Rechnen und Schaltnetze

Anlegen eines Speicherbereichs mit DB, DW eleganter in Kombination mit EQU, Timer-Interrupt

Skript und Aufgabensammlung Terme und Gleichungen Mathefritz Verlag Jörg Christmann Nur zum Privaten Gebrauch! Alle Rechte vorbehalten!

Motivation. Formale Grundlagen der Informatik 1 Kapitel 5 Kontextfreie Sprachen. Informales Beispiel. Informales Beispiel.

1 Vom Problem zum Programm

Übung 9 - Lösungsvorschlag

Binäre Gleitkommazahlen

Computerarithmetik ( )

1 topologisches Sortieren

Formale Sprachen und Grammatiken

M. Graefenhan Übungen zu C. Blatt 3. Musterlösung

Konzepte der Informatik

Erweiterung der Aufgabe. Die Notenberechnung soll nicht nur für einen Schüler, sondern für bis zu 35 Schüler gehen:

1. Formale Sprachen 1.2 Grammatiken formaler Sprachen

7 Rechnen mit Polynomen

Lineare Gleichungssysteme

Einfache Ausdrücke Datentypen Rekursive funktionale Sprache Franz Wotawa Institut für Softwaretechnologie

Stellen Sie bitte den Cursor in die Spalte B2 und rufen die Funktion Sverweis auf. Es öffnet sich folgendes Dialogfenster

Repetitionsaufgaben Wurzelgleichungen

Programmieren in C. Felder, Schleifen und Fließkommaarithmetik. Prof. Dr. Nikolaus Wulff

Übungskomplex Felder (1) Eindimensionale Felder Mehrdimensionale Felder

DHBW Karlsruhe, Vorlesung Programmieren, Klassen (2)

Einführung in die Informatik I

Sowohl die Malstreifen als auch die Neperschen Streifen können auch in anderen Stellenwertsystemen verwendet werden.

Lösungen: zu 1. a.) b.) c.)

Mikro-Controller-Pass 1

Anmerkungen zur Übergangsprüfung

Informationsblatt Induktionsbeweis

Repetitionsaufgaben Negative Zahlen/Brüche/Prozentrechnen

Grundlagen der Informatik I Informationsdarstellung

Das RSA-Verschlüsselungsverfahren 1 Christian Vollmer

6.2 Scan-Konvertierung (Scan Conversion)

Lineare Gleichungssysteme

Mediator 9 - Lernprogramm

Übungsaufgaben. - Vorgehensweise entsprechend dem Algorithmus der schriftlichen Multiplikation

Diana Lange. Generative Gestaltung Operatoren

Übungen Programmieren 1 Felix Rohrer. Übungen

1.Unterschied: Die Übungen sind nicht von deinem Mathe-Lehrer...

N Bit binäre Zahlen (signed)

15 Optimales Kodieren

Programmierung mit NQC: Kommunikation zwischen zwei RCX

Informatik A ( Frank Hoffmann)

Plotten von Linien ( nach Jack Bresenham, 1962 )

VBA-Programmierung: Zusammenfassung

Tangentengleichung. Wie lautet die Geradengleichung für die Tangente, y T =? Antwort:

4 Binäres Zahlensystem

Semantik von Formeln und Sequenzen

Aufgaben zu Stellenwertsystemen

1. LPC - Lehmanns Programmier Contest - Lehmanns Logo

Tipp III: Leiten Sie eine immer direkt anwendbare Formel her zur Berechnung der sogenannten "bedingten Wahrscheinlichkeit".

Wir basteln einen Jahreskalender mit MS Excel.

Transkript:

Informatik für Nebenfächler Priv.-Doz. Dr. Frank Huch Institut für Informatik, Technische Fakultät, Christian-Albrechts-Universität zu Kiel. Skript zur Vorlesung im Wintersemester 2011/12.

Stand vom: 10. Februar 2012 Eine erste Version dieses Skripts wurde durch Jana Bork erstellt. Anmerkungen und Korrekturen bitte an fhu@informatik.uni-kiel.de. Freundlichem Dank an Torsten Krause für das Layout der Titelseite.

Inhaltsverzeichnis 1 Einleitung 5 2 Technische Grundlagen der Informatik 6 2.1 Rechnerarchitektur.............................. 6 2.2 Zahlensysteme und binäre Arithmetik................... 8 2.2.1 Umwandlung von Dezimalzahlen in Binärzahlen......... 10 2.2.2 Rechnen mit Binärzahlen...................... 11 2.2.3 Einerkomplement/Stellenkomplement............... 13 2.2.4 Zweierkomplement.......................... 14 2.2.5 Brüche (rationale Zahlen)...................... 15 2.2.6 Gleitpunktzahlen (floating point numbers)............. 16 2.2.7 Grundbegriffe............................. 17 3 Programmierung 19 3.1 Ausdrücke................................... 19 3.2 (Erweiterte) Backus-Naur-Form (BNF und EBNF)............ 20 3.2.1 Termdarstellungen.......................... 23 3.2.2 Ausdrücke zur Implementierung einfacher Programme...... 27 3.2.3 Boolesche Werte........................... 28 3.3 Anweisungen................................. 30 3.3.1 while-schleife:............................ 32 3.3.2 Größter gemeinsamer Teiler..................... 35 3.3.3 Aufzählen und Testen........................ 35 3.3.4 Euklidischer Algorithmus...................... 37 3.3.5 for-schleife.............................. 40 3.3.6 Ausdrucksstärke unterschiedlicher Statements........... 44 3.4 Objektorientierte Programmierung..................... 46 3.5 Zeichenketten................................. 48 3.6 Abstraktion durch Prozeduren....................... 51 3.7 Rekursion................................... 53 3.8 Objekte und ihre Identität.......................... 55 3.9 Objektorientierte Datenmodellierung.................... 58 4 Datenstrukturen und Algorithmen 64 4.0.1 Lese- und Schreibzugriff auf Dateiinhalten............. 68 4.1 Reguläre Ausdrücke............................. 69 4.1.1 Backtracking............................. 73 4.2 Internet und WWW............................. 79 4.2.1 HTML................................. 80 4.3 Sortieren.................................... 83 4.3.1 Sortieren durch Auswählen..................... 84 4.3.2 Sortieren durch Einfügen...................... 86 4.4 Die Fibonacci-Funktion........................... 88 3

Inhaltsverzeichnis 4.5 O-Notation.................................. 89 4.6 Suchen von Elementen............................ 89 4.6.1 Binäre Suche............................. 91 4.7 Effizientes Sortieren............................. 92 4.7.1 Andere effiziente Sortieralgorithmen................ 95 4.8 Schwere Probleme.............................. 96 4.8.1 Halteproblem............................. 97 4.9 Besonderheiten von Ruby.......................... 98 4.9.1 Semikolon............................... 98 4.9.2 Anweisungen und Ausdrücke.................... 99 4.9.3 Zuweisung statt Gleichheit..................... 100 4.10 Datenkompression.............................. 101 4

1 Einleitung Die Informatik ist die Wissenschaft von der systematischen Verarbeitung von Informationen, insbesondere die automatische Verarbeitung mit Rechenanlagen (Computer). Ursprünge Mathematik Berechnen von Folgen, Lösung von Gleichungssystemen Elektrotechnik Computer als Weiterentwicklung von Schaltkreisen Nachrichtentechnik Datenübertragung im Rechner oder im www Disziplinen der Informatik Theoretische Informatik Grundlagen, z.b. Komplexitätstheorie, Berechenbarkeit, Graphentheorie Technische Informatik Hardwarenahe Aspekte, z.b. Mikroprozessortechnik, Rechnerarchitekturen, Netzwerksysteme Praktische Informatik Lösen von konkreten Problemen durch Algorithmen/Programme hierbei wichtige Aspekte: Effizienz, Softwaretechnik, Programmiersprachen, Datenbanken 5

2 Technische Grundlagen der Informatik Erste Computer wurden von Konrad Zuse 1937 konstruiert: Z1 - mechanisches Rechenwerk - Kontrolleinheit, Speicher, Mikrobefehle, Fließkommaberechnung Erster elektronischer Rechner 1941: Z3 - Trennung von Befehls- und Datenspeicher - Ein- / Ausgabepult Parallel fanden Entwicklungen in Großbritannien und den USA statt. Wichtigster Akteur hierbei: John von Neumann der die prinzipiell heute noch verwendete Von- Neumann-Architektur von Computern entwarf. Zu Beginn der 60er Jahre lösten Transistoren die Röhrentechnik ab, wodurch die Leistungsfähigkeit extrem erhöht wurde. Die Mikroprozessortechnik auf Siliziumbasis ermöglichte dann in den 70er Jahren eine zunehmende Miniaturisierung der Computer. Heutzutage werden Prozessoren nicht nur in herkömmlichen PCs oder Großrechnern eingesetzt, sondern zunehmend als integrierte Schaltkreise in z.b. Waschmaschinen, Aufzügen oder Autos (mehr als 100 Prozessoren möglich), sogenannte eingebettete Systeme. 2.1 Rechnerarchitektur Die meisten Computer basieren auf der Von-Neumann-Architektur, welche in den 40er Jahren entwickelt wurde. Komponenten: - Zentralprozessor (CPU) Steuerwerk: Abarbeitung der Maschinenbefehle Rechenwerk (ALU - Arithmetic Logical Unit) Register: Speichern einzelner Werte - Arbeitsspeicher(beliebige Daten, die unterschiedlich interpretiert werden können) auszuführende Programme in Maschinencode Festwertspeicher (ROM) mit wichtigen Basisroutinen Datenspeicher zur Speicherung von Datenstrukturen einzelner Programme 6

2.1 Rechnerarchitektur - Bus (Verbindung zwischen Speicher und Prozessor) Datenbus zum Transport der Daten vom Speicher zur CPU und zurück Adressbus zum Einstellen der Adresse, aus der Daten geholt werden sollen oder geschrieben werden sollen Steuerbus um den Speicher vorzubereiten, um entsprechende Daten liefern zu können oder zu speichern Genereller Aufbau: Weitere Komponenten: Tastatur, Grafik- und Soundkarte, externe Speichermedien In der Regel auch über Bussysteme an das Hauptsystem angeschlossen, so dass ebenfalls Werte adressiert und ausgelesen werden können. In der Regel aber nur Adress- und Datenbus, kein Steuerbus. Beachte aber, dass das Bussystem zu Massenspeichermedien in der Regel um ein Vielfaches langsamer ist, als zum Speicher. Zugriff geregelt über das Betriebssystem, so dass unterschiedliche Komponenten gleich angesprochen werden können (Treiber). Grafik- und Soundkarten sind eigentlich wieder eigene Computer z.t. mit Prozessor und Speicher, oder auch in CPU/Mainboard integriert. Weitere Aspekte: - Taktfrequenz (z.b. 200 MHz) gibt an, wie viele Operationen ein Computer pro Sekunde ausführen kann. Allerdings benötigen viele Maschinenbefehle mehrere 7

2 Technische Grundlagen der Informatik Schritte. Zur Zeit wenig Verbesserungen wegen physikalischen Grenzen. Moores Law: Verdopplung der Rechenleistung alle 1 1 2 Jahre (gilt seit 1940!) Heute Performancesteigerung durch mehrere Prozessorkerne - Cache: Schneller in den Prozessor integrierter Speicher, der Teile des Hauptspeichers spiegelt. Ermöglicht nicht immer Beschleunigung (Betriebssystem, Anwendung) - Multitasking: Mehrere Programme können gleichzeitig auf einem Computer ausgeführt werden. Verteilung der Prozessorzeit durch Scheduler auf BS-Ebene sehr wichtig bei graphischen Benutzeroberflächen - Multicore-Architektur: Mehrere Prozessoren (meist 2 oder 4) können über Bus auf Speicher zugreifen. Ausnutzung für eine Anwendung schwer parallele Algorithmen 2.2 Zahlensysteme und binäre Arithmetik In Digitalrechnern werden eigentlich nur zwei Werte (Ziffern) kodiert: 0 = kein Strom 1 = Strom } Dieser Wert wird auch Bit (binary digit) genannt. Aufbauend hierauf können kompliziertere Werte, wie allgemeine Zahlen, Zeichen oder auch Prozessorbefehle kodiert werden. Das Dezimalsystem (dekadisches Ziffernsystem, natürliche Zahlen) Zahlen des Dezimalsystems können dargestellt werden als n = z k 10 k +...+z 1 10 1 +z 0 10 0 mit Ziffern z i {0,1,...,9} für 0 i k Beispiel: 1278 = 1 10 3 +2 10 2 +7 10 1 +8 10 0 Um eine eindeutige Darstellung zu haben, lässt man führende Nullen in der Regel weg (z k 0) außer z 0 = 0. Betrachtet man nur die Ziffern 0 und 1, so kann man ganz analog Zahlen des binären Ziffernsystems oder Binärsystems definieren: Binärsystem mit Ziffern z i {0,1} für 0 i k n = z k 2 k +...+z 1 2 1 +z 0 2 0 8

2.2 Zahlensysteme und binäre Arithmetik Beispiel: 330 = 1 2 8 +0 2 7 +1 2 6 +0 2 5 +0 2 4 +1 2 3 +0 2 2 +1 2 1 +0 2 0 ( = 256+64+8+2 = 330) Binärzahlen können natürlich auch kompakt wie Dezimalzahlen notiert werden: 330 dez = 101001010 bin. Hierbei notieren wir das Zahlensystem als Index um Verwechslungen auszuschließen: 100 dez 100 bin. Bei Dezimalzahlen lassen wir das Zahlensystem aber oft weg. Neben dem Dezimal- und dem Binärsystem können beliebige weitere n-adische Ziffernsysteme definiert werden. Wichtig ist noch das Hexadezimalsystem (16-adisches Ziffernsystem): Hexadezimalsystem Als Ziffern verwendet man in der Regel {0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F}. Beispiel: 1278 dez = 4 16 2 +15 16 1 +14 16 0 = 4FE hex ( = 4 256+15 16+14 1 = 1024+240+14 = 1278) NR: 1278 : 16 = 79 R14 = E 79 : 16 = 4 R15 = F 4 : 16 = 0 R 4 = 4 Warum ist dieses Zahlensystem wichtig? Da 16 eine Zweierpotenz ist und damit ein Vielfaches von 2, können Ziffernblöcke des Binärsystems eindeutig auf Hexadezimalziffern abgebildet werden: Binär Hexadezimal Binär Hexadezimal 0 0 1000 8 1 1 1001 9 10 2 1010 A 11 3 1011 B 100 4 1100 C 101 5 1101 D 110 6 1110 E 111 7 1111 F Somit können Binärzahlen sehr einfach in Hexadezimalzahlen umgewandelt werden und umgekehrt. 9

2 Technische Grundlagen der Informatik Beispiel: 330 dez = }{{} 1 0100 }{{} 1010 }{{} bin = 14A hex 1 4 A 1278 dez = 4FE hex = }{{} 100 1111 }{{} 1110 }{{} bin 4 F E Gleiches gilt für das Oktalsystem (8-adische Zahlensystem), da 8 = 2 3. In der Praxis wird dieses allerdings seltener verwendet, da es Zahlen weniger kompakt kodiert. 2.2.1 Umwandlung von Dezimalzahlen in Binärzahlen Beispiel: Also 276 dez = }{{} 10001010 }{{} bin = 8A hex. 8 A 276 = 256+20 = 2 8 +20 20 = 16+4 = 2 4 +4 4 = 4 = 2 2 Unschön: Man benötigt alle 2er-Potenzen und muss diese schrittweise(beginnend mit der grössten passenden) abziehen. Alternative: Horner-Schema Horner-Schema n = z k 2 k +...+z 1 2 1 +z 0 2 0 = (z k 2 k 1 +...+z 1 2 0 ) 2+z 0 = ((z k 2 k 2 +...+z 2 2 0 ) 2+z 1 ) 2+z 0 =... = ((...((z k 2+z k 1 ) 2+z k 2 ) 2+...)+z 2 ) 2+z 1 ) 2+z 0 Als Beispiel können wir schreiben: 330 dez = ((...(1 2+0) 2+1) 2+0) 2+0) 2+1) 2+1) 2+1) 2+0 D.h. um diese 1 zu erhalten müssen wir umgekehrt 8 mal durch 2 dividieren, Der Rest ergibt jeweils die 1 oder die 0. 330 : 2 = 165 R0 165 : 2 = 82 R1 82 : 2 = 41 R0 41 : 2 = 20 R1 20 : 2 = 10 R0 10 : 2 = 5 R0 5 : 2 = 2 R1 2 : 2 = 1 R0 1 : 2 = 0 R1 höchstes Bit 101001010 10

2.2 Zahlensysteme und binäre Arithmetik Entsprechend können wir auch ins Hexadezimalsystem umrechnen: 330 : 16 = 20 R10 20 : 16 = 1 R4 1 : 16 = 0 R1 Also 14A hex Dieser Algorithmus läßt sich für beliebige n-adische Zahlensysteme verallgemeinern. 2.2.2 Rechnen mit Binärzahlen Das Prinzip ist identisch mit dem im Dezimalsystem: Addition: 0+0 = 0 0+1 = 1 1+0 = 1 1+1 = 10 (oder 0 mit 1 Übertrag) Für die Stelle entspricht + also dem booleschen XOR und der Übertrag dem AND Dann: 1278 dez 10011111110 bin + 330 dez + 101001010 bin 1608 dez }{{} 110 0100 }{{} 1000 }{{} bin 6 4 8 hex Probe: 1608 : 16 = 100 R8 100 : 16 = 6 R4 6 : 16 = 0 R6 = 648 hex Subtraktion: 0 0 = 0 0 1 = 1 Übertrag 1 1 0 = 1 1 1 = 0 Dann: 1278 dez 10011111110 bin 330 dez 101001010 bin 948 dez }{{} 111011 }{{} 0100 }{{} bin = 3B4 hex 3 4 hex 11=B Probe: 948 : 16 = 59 R4 59 : 16 = 3 R11 3 : 16 = 0 R3 = 3B4 hex 11

2 Technische Grundlagen der Informatik Multiplikation von Binärzahlen: Beispiel: 1 0 1 1 0 1 1 1 0 1 1 0 1 1 0 1 1 1 0 1 1 1 Implementierbar durch schrittweises Addieren und Shiften ( = Verdoppeln). Im Beispiel: 0 + 1 0 1 1 1 0 1 shift 1 0 1 0 shift 1 0 1 0 0 0 + 1 0 1 1 1 1 0 0 1 shift 1 1 0 0 1 0 + 1 0 1 1 1 1 0 1 1 1 Division auch einfach möglich: 1 1 0 1 1 1 : 1 0 1 = 1 0 1 1-1 0 1 1 1 1-1 0 1 1 0 1-1 0 1 0 Bem: Das kleine 1 1 ist hier sehr übersichtlich: 0 0 = 0 0 1 = 0 1 0 = 0 1 1 = 1 Das hier vorgestellte Verfahren zur Addition/Subtraktion durchläuft beide Zahlen schrittweise von hinten nach vorne. Im Prozessor wird dies parallelisiert. Hierzu in der Regel Verwendung von Zahlen fester Stelligkeit (früher 8 Bit = 1 Byte, heute eher 16 32, 64 Bit) (Datenwort). =1DOUBLE WORD =1Quadrupelword Zusatzinhalt, der für 5 ECTSler nicht relevant ist Mit 8 Bit können die Zahlen von 0 dez bis 255 dez kodiert werden: 0 dez = 00000000 bin 128 dez = 10000000 bin 255 dez = 11111111 bin =1WORD, 12

2.2 Zahlensysteme und binäre Arithmetik Wie können dann auch negative Zahlen dargestellt werden? Erste Idee: verwende erstes Bit als Vorzeichen, also 32 dez = 00100000 bin 48 dez = 10110000 bin } 11010000 bin = 80 dez Dann können negative Zahlen aber leider nicht so einfach addiert werden. Betrachte eine einfache Aufgabe (hier Datenwörter mit 4 Bit): 1+( 2) = ( 1) also 0001 bin + 1010 bin 1011 bin = 3 dez 1+( 1) = 0 also 0001 bin + 1001 bin 1010 bin = 2 dez Es wäre also besser, wenn die negativen Zahlen anders herum sortiert wären: 2.2.3 Einerkomplement/Stellenkomplement Also: 7 = 1000 3 = 1100 6 = 1001 2 = 1101 5 = 1010 1 = 1110 4 = 1011 0 = 1111? Beachte: Negative Zahlen durch Inverses der Positiven. kleinste negative Zahl ( 7) = 1000 und größte negative Zahl ( 1) = 1110 1+( 7) = 6 also 0001 + 1000 1001 = 6 dez 1+( 2) = 1 also 0001 + 1101 1110 = 1 dez 6+( 2) = 4 also 0110 + 1101 10011 1 shift around carry 0100 Beachte: Es kann auch ein Übertrag auftreten Übung 1+( 1) = 0 aber 0001 + 1110 1111 = 0 Aber nicht 0000. D.h. wir haben zwei Darstellungen für die Null, was unschön ist. Lösung: Verschiebe negative Zahlen um eins (Zweierkomplement) 13

2 Technische Grundlagen der Informatik 2.2.4 Zweierkomplement 7 = 1001 3 = 1101 6 = 1010 2 = 1110 5 = 1011 1 = 1111 4 = 1100 0 = 0000 Beachte: hierdurch wird die 1000 frei und kann als 8 interpretiert werden. Dann: 1+( 7) = 6 also 0001 + 1001 1010 = 6 dez 1+( 1) = 0 also 0001 + 1111 10000 = 0 dez Übertrag wird verworfen Somit kann bei Verwendung des Zweierkomplements die Subtraktion auf die Addition zurückgeführt werden. Beachte: bei Bereichsüberschreitung gibt es einen Überlauf: 6 dez +6 dez = 12 dez, was mit 4 Bit mit Vorzeichen nicht darstellbar ist: 0110 + 0110 1100 = 4 dez Negieren einer Zahl im Zweierkomplement: Einerkomplement (Inverses) + 1! Gilt in beide Richtungen: Allgemein gilt: mit n Stellen können die Zahlen 2 n 1 bis 2 n 1 1 dargestellt werden. { + + Beachte, dass dieses Verfahren auch in anderen Zahlensystemen funktioniert: Beispiel: 493 hat das Stellenkomplement 999 493 = 506 (Datenwort von 3 Dezimalziffern). Das Zehnerkomplement ergibt sich dann als 506+1 = 507. Aufgabe: 385 493 = 385+( 493) = 108 Rechnung mit Zehnerkomplement: Beachte: 892 500, also negativ. 385 + 507 892 14

2.2 Zahlensysteme und binäre Arithmetik Das Ergebnis 108 hat das Stellenkomplement 999 108 = 891. Also im Zehnerkomplement 891+1 = 892 Oder anders herum: 892 Stellenkomplement 107 + 1 = 108, also 892 = 108. Übung: Umwandlung Zweierkomplement/Zehnerkomplement in wirkliche negative Zahl. 2.2.5 Brüche (rationale Zahlen) Häufig verwendet man zum Rechnen Dezimalbrüche (z.b. 1, 56 oder 2, 3462). Diese können nach dem gleichen Schema wie bisher in andere Zahlensysteme übertragen werden. Beispiel: 0,6875 dez = 1 2 1 +0 2 2 +1 2 3 +1 2 4 = 0,1011 bin Berechnung mit Horner-Schema: 0,6875 2 = 1,375 1 0,375 2 = 0,75 0 0,75 2 = 1,5 1 0,5 2 = 1,0 1 Bem: 0,6875 16 = 11 dez 11 dez = B (kein Rest!) 1 2 1 +0 2 2 +1 2 3 +1 2 4 = (1 2 2 +0 2 3 +1 2 4 ) : 2+1 2 4 = ((1 2 3 +0 2 4 ) : 2+1 2 4 )+1 2 4 = (((1 2 4 ) : 2+0 2 4 ) : 2+1 2 4 ) : 2+1 2 4 0,01 = 1 2 2 +0 2 1 = (1 2 1 ) : 2+0 2 1 0,011 = 1 2 3 +1 2 2 +0 2 1 = (1 2 2 +1 2 1 ) : 2+0 2 1 = ((1 2 1 ) : 2+1 2 1 ) : 2+0 2 1 Nachteil dieser Darstellung: 0,2 dez 2 = 0,4 0 0,4 2 = 0,8 0 0,8 2 = 1,6 1 0,6 2 = 1,2 1 0,2 0,2 dez = 0,0011 D.h. endliche Dezimalbrüche werden als Binärbruch periodisch. Weiteres Problem, wie kann man Binärbrüche in feste Datenwortgröße packen? Lösung: Gleitpunktzahlen (floating point numbers) 15

2 Technische Grundlagen der Informatik 2.2.6 Gleitpunktzahlen (floating point numbers) Idee: Man beschränkt sich auf Zahlen einer bestimmten Genauigkeit, d.h. unterscheidbarer Stellen. Beispiel: Genauigkeit vier Stellen: 1234 23, 45 34560000 0, 0004567 Beachte: alle Zahlen könnten auch Zahlen einer größeren Genauigkeit sein. Für eine klarere Darstellung der Genauigkeit schreibt man besser: 1234 10 0 2345 10 2 3456 10 4 4567 10 7 oder 0,1234 10 4 0,2345 10 2 0,3456 10 8 0,4567 10 3 Analog für Binärzahlen (Float, 4 Byte) Lichtgeschwindigkeit e = 2,99792458 10 8 m/s = 2, 99792458e8m/s Beachte: Durch Rechnen mit Genauigkeit können Rundungsfehler auftreten Beispiel: 1234 10 0 +2345 10 2 = 1257 10 0 3456 10 4 +2345 10 2 = 3456 10 4 Somit Floats niemals auf Gleichheit, Ungleichheit testen! Wofür werden im Rechner alles Zahlen verwendet? - Zum Rechnen: positive Zahlen fester Stelligkeit ganze Zahlen fester Stelligkeit (Zweierkomplement) 16

2.2 Zahlensysteme und binäre Arithmetik Floating Point Zahlen (als Näherung für rationale Zahlen) - Als Adressen: positive Zahlen Speicherplätze sind durchnummeriert von 000...0 bis maximal 111...1. Bei 32 Bit-Architekturen also maximal 2 32 1 Speicherzellen 4,3 Milliarden Bytes = 4 GigaByte (GB) - Als Maschinencode: z.b. Sprung- oder Ladebefehle. Zusatzinhalt, der für 5 ECTSler nicht relevant ist 2.2.7 Grundbegriffe Algorithmus ist die Beschreibung einer Vorgehensweise zur Lösung von Problemen. Beispiele Kochrezept, Berechnung der Quadratwurzel, Dekodierung von DNA-Sequenzen, Berechnung von π, Verwaltungsvorschriften Beachte dabei: - in der Regel löst ein Algorithmus eine Klasse von Problemen (d.h. er ist parameterisiert), z.b. Quadratwurzel für beliebige Zahlen, Verwaltungsvorschrift für Hausbau. Parameter legt konkretes Problem der Klasse fest. - die Vorgehensweise kann unterschiedlich detailliert formuliert werden, z.b. beim Kochrezept: Eiweiß steif schlagen oder Schüssel holen, Eiweiß und Eigelb trennen, Schneebesen nehmen,... Es ist eine Kunst den richtigen Grad an Genauigkeit zu finden. Wichtig hierbei: von Details abstrahieren. Abstraktion ist eines der wichtigsten Konzepte der Informatik. Beispiel Zählen von Weizenkörnern (alle gleich schwer), Apothekerwaage (ohne Gewichte), Papier, Bleistift Algorithmus - Teile Körner in zwei gleichschwere Haufen - Ist dies nicht möglich lege ein Korn zur Seite. Danach kannst Du sie aufteilen. { 1, falls du ein Korn zur Seite gelegt hast - Beschrifte den Zettel nach und nach wie folgt: 0, sonst - Wenn kein Korn mehr übrig ist, drehe die Zahl auf dem Zettel um und lies das Ergebnis als Binärzahl ab. Ggf. kannst du diese Zahl mit Horner-Schema in eine Dezimalzahl wandeln - sonst verfahre mit einem der beiden Haufen wie zuvor. Beispiel 13 : 2 = 6 R 1 6 : 2 = 3 R 0 3 : 2 = 1 R 1 1 : 2 = 0 R 1 1101b = 13d Algorithmen werden in der Informatik häufig mit Hilfe von Programmiersprachen programmiert, so dass sie auf Rechnern/Computern ausgeführt werden können. Hierbei 17

2 Technische Grundlagen der Informatik ist zunächst keine Abstraktion mehr möglich. Ein Programm muss so konkret sein, dass ein Prozessor (winziger Befehlssatz) den Algorithmus ausführen kann. Programmierung zunächst nur in Maschinensprache. Feste Abstraktionen der höheren Programmiersprachen ermöglichen elegantere Programmierung auf abstrakterem Niveau. (In dieser Vorlesung werden wir die Sprache Ruby kennenlernen.) Weitere Abstraktion durch Programmiertechniken (Softwaretechnik) möglich. Programmierung ist heute die Kunst/Technik ein Problem und seine Lösung in Teile zu zerlegen, so dass sich ein kompositionelles System ergibt, welches leicht zu verstehen, zu ändern und zu konfigurieren ist. Programmiersprachen ermöglichen die Kommunikation mit dem Rechner auf möglichst natürliche Weise. Programme werden entweder mit Hilfe eines Compilers in Maschinensprache übersetzt (z.b. C) oder durch ein spezielles Programm (Interpreter) interpretiert (z.b. Ruby). Mischformen sind ebenfalls möglich. Bei der Programmierung unterscheidet man drei Bereiche: - Syntax beschreibt die zulässigen Zeichenfolgen der Programme - Semantik beschreibt, wie die Programme ausgeführt werden, also die Bedeutung der Sprachkonstrukte - Pragmatik beschreibt die Idee, wie die Programmiersprache verwendet werden soll (viele Wege führen nach Rom, aber welche sind gut). Hier spielt auch das Programmieren als Kunst/Technik hinein. Beachte, dass bisher wenig über die Effizienz von Programmen gesagt wurde. Diese ist in der Regel unwichtig! Wenige Ausnahmen: zeitkritische Algorithmen Wichtiger meist: Verständlichkeit, Wartbarkeit, Entwicklungszeit 18

3 Programmierung Algorithmus: abstrakte Beschreibung zur Lösung von Problemen Die meisten Algorithmen können automatisch durch Computer ausgeführt werden. Hierzu muss der Algorithmus in einer konkreten Programmiersprache implementiert werden. Zunächst wollen wir ein paar grundlegende Konzepte kennenlernen, die in vielen Programmiersprachen verwendet werden und mit deren Hilfe wir einfachste Algorithmen programmieren können. 3.1 Ausdrücke Aus der Mathematik kennt man Ausdrücke Woraus bestehen Ausdrücke? Basiselemente: - Werte (Konstanten) z.b. 3,4 N oder π R 3+4 x 2 +2x+1 (x+1) 2 - Variablen z.b. x,y repräsentieren beliebige Werte und können später mit konkreten Werten belegt werden. Zusammengesetzte Ausdrücke erhält man durch die Anwendung von Funktionen, z.b. +,, ( in der Informatik) auf bereits gebildete Ausdrücke. Diese Funktionen (auch Operatoren genannt) sind zweistellig und verknüpfen zwei Ausdrücke zu einem neuen Ausdruck: 3+4 Was ist aber mit x 2 +1 x? Auch hier finden wir mehrere Funktionsanwendungen, die aber ungewöhnlich notiert werden. Der Computer (die Programmiersprache) erwartet genormte Darstellung solcher Ausdrücke, deshalb somit können wir x 2 +1 x schreiben als: x 2 statt x 2 sqrt(x) statt x a a/b statt b sqrt(x 2+1)/x 19

3 Programmierung Vergleiche dies mit sqrt(x (2+1))/x Gibt es einen Unterschied? Ja, da die Funktion stärker bindet als +. Legt man also Präzedenzen (z.b. Punktvor-Strich-Rechnung: und / binden stärker als + und!) für die Funktionen fest, kann man auf Klammern verzichten. Wenn man solche Klammern wegläßt, werden durch den Computer (z.b. in der Programmiersprache oder Tabellenkalkulation) in der Regel die fehlenden Klammern hinzugefügt. Die vollständig geklammerte Schreibweise für unseren Ausdruck wäre also: (sqrt(((x 2)+1))/x) Jede Operatoranwendung wird geklammert. Als nächstes wollen wir einen Formalismus kennenlernen, mit welchem Sprachen formal beschrieben werden können und welchen wir im weiteren Verlauf der Vorlesung verwenden werden um die Syntax unserer Programmiersprache Ruby zu verstehen. 3.2 (Erweiterte) Backus-Naur-Form (BNF und EBNF) Die Backus-Naur-Form (BNF) stellt einen Formalismus zur formalen Beschreibung von Sprachen (insbesondere Programmiersprachen) dar. Es werden Nichtterminalsymbole (beginnen mit Großbuchstaben) und Terminalsymbole (Zeichen in ) unterschieden. Nichtterminalsymbole stellen keine Elemente der zu definierenden Sprache dar. Vielmehr sind sie Strukturelemente, welche weiter verfeinert und letzendlich zu einer Folge von Terminalsymbolen abgeleitet werden. Als erstes Beispiel betrachten wir eine BNF, die die Sprache aller vollständig geklammerten Ausdrücke Exp beschreibt: Exp ::= Var Val ( Exp Op Exp ) Fun ( Exps ) Exps ::= Exp Exp, Exps Op ::= + / Fun ::= sqrt Val ::= Num if then else Num Num ::= Digit Digit Num Digit ::= 0... 9 Var ::= x y z Aus einer BNF kann man gültige Wörter schrittweise ableiten. Hierbei beginnt man mit dem Nichtterminalsymbol, aus dem man ein Wort ableiten möchte. Als Beispiel 20

3.2 (Erweiterte) Backus-Naur-Form (BNF und EBNF) wollen wir zeigen, dass obiges Wort (sqrt(((x 2)+1))/x) ein vollständig geklammeter Ausdruck ist, d.h. dieses Wort aus dem Nichtterminal Exp abgeleitet werden kann. In jedem Ableitungsschritt darf jeweils nur ein vorkommendes Nichtterminalsymbol durch eine seiner rechten Seiten ersetzt werden. Kommen keine Nichtterminalsymbole mehr vor, hat man ein gültiges Wort der beschriebenen Sprache abgeleitet: Exp (Exp Op Exp) (Exp / Exp) (Exp / Var) (Exp / x) (Fun(Exps) / x) (Fun(Exp) / x) (sqrt(exp) / x) (sqrt((exp Op Exp)) / x) (sqrt((exp+exp)) / x) (sqrt((exp+val)) / x) (sqrt((exp+num)) / x) (sqrt((exp+digit)) / x) (sqrt((exp+1)) / x) (sqrt(((exp Op Exp)+1)) / x) (sqrt(((var Op Exp)+1)) / x) (sqrt(((x Op Exp)+1)) / x) (sqrt(((x Exp)+1)) / x) (sqrt(((x Val)+1)) / x) (sqrt(((x Num)+1)) / x) (sqrt(((x Digit)+1)) / x) (sqrt(((x 2)+1)) / x) Da diese Ableitungen häufig sehr aufwendig sind und sich immer nur wenig an den einzelnen Wörtern ändert, ist es oft praktischer die Ableitung in Baumform darzustellen. Wir nennen diese Ableitungsbaum für ein Wort. Als Beispiel hier der Ableitungsbaum für das Wort (3+x): Exp ( Exp Op Exp ) Val + Var Num x Digit 3 Die Nichtterminalsymbole bilden die inneren Knoten des Ableitungsbaums. Die Wurzel beschriften wir mit dem Nichtterminalsymol, aus welchem man das Wort ableiten möchte(hier z.b. Exp). Die Kinder eines Nichtterminalknotens, entsprechen jeweils den Symbolen der rechten Seite der verwendeten Regel. So wurde bei der Wurzel zunächst dieregelexp ::= ( ExpOpExp ) angewendet,weshalbdiewurzelfünfkindknoten hat, die von links nach rechts mit den entsprechenden Symbolen beschriftet sind. 21

3 Programmierung Im fertigen Ableitungsbaum sind alle Blätter mit Terminalsymbolen beschriftet. Das abgeleitete Wort ergibt sich, indem man die Front des Baumes von links nach rechts abliest, hier also das Wort (3+x). Die hier vorgestellte BNF werden wir auch in der weiteren Vorlesung wiederverwenden und auch noch erweitern. Um aber klar zu machen, dass der Formalismus BNF ein universeller Formalismus zur Beschreibung beliebiger Sprachen ist, betrachten wir als weiteres Beispiel die Sprache der Palindrome. Ein Palindrom ist ein Wort, welches von vorne und von hinten gelesen gleich ist. Beispiele sind otto, rentner oder (wenn man die Leer-/Satzzeichen ignoriert) o genie, der herr ehre dein ego. Wenn man Palindrome formal spezifizieren will, so kann man dies mit Hilfe folgender EBNF machen: Pal ::= a Pal a... z Pal z a... z ε Hierbei werden natürlich nicht nur gültige Palindrome der deutschen Sprache beschrieben, sondern vielmehr alle Wörter (über dem Alphabet a bis z ), die von vorne und hinten gleich aussehen. Die letzte Regel P al ::= ε wird verwendet um Palindrome ohne einen einzelnen Buchstaben in der Mitte zu bilden. ε steht hierbei für das leere Wort, also ein Wort der Länge 0, welches keine Zeichen enthält. Untersucht man die BNF für Terme genauer, fällt auf, dass immer wieder ähnliche Konstruktionen auftreten, wie z.b. das optionale Vorkommen oder die Wiederholung von Teilausdrücken. Um solche Strukturen einfacher ausdrücken zu können wurde die BNF um spezielle Konstrukte zur erweiterten BNF (EBNF) erweitert: Optionales Vorkommen eines Wertes: [e] Dann für unsere Definition von Ausdrücken: Val ::= [ ] Num Optionale Wiederholung {e}, d.h. e kann 0-mal, 1-mal, 2-mal,... vorkommen. Außerdem besteht noch die Möglichkeit die Alternative( ) auch in Gruppierungen zu verwenden. Ein Beispiel hierzu ist mit abd und acd aus S ableitbar. S ::= a ( b c ) d Mit diesen Abkürzungen können wir unsere BNF an einigen Stellen kompakter hinschreiben: Exp ::= Var Val ( Exp Op Exp ) Fun ( Exp {, Exp } ) Num ::= Digit { Digit } 22

3.2 (Erweiterte) Backus-Naur-Form (BNF und EBNF) Zusatzinhalt, der für 5 ECTSler nicht relevant ist Stellt diese Erweiterung eine echte Erweiterung dar, d.h. können Sprachen/Eigenschaften beschrieben werden, welche vorher nicht möglich waren? Nein, denn jede EBNF kann in eine BNF übersetzt werden, welche die gleiche Sprache beschreibt. Gehe hierzu wie folgt vor: Falls es eine Regel gibt mit N ::= α [ β ] γ Dann ersetze diese durch Falls es eine Regel gibt mit Dann ersetze diese durch N ::= α γ α β γ N ::= α { β } γ N ::= α M γ M ::= β M ε, wobei M ein neues Nichtterminalsymbol der EBNF ist, also noch nicht in der aktuellen EBNF verwendet worden sein darf. Die BNF wird auch von Compilern zur Analyse der Programmiersprache verwendet, Aufgabe ist es hierbei zu einem gegebenen Wort (Programm) einen passenden Ableitungsbaum zu konstruieren. Hierbei kann der Compiler alle möglichen Ableitungen ausprobieren: Bsp: Finde Ableitung für (3+4) Exp Var Num Digit Digit Num 0 1... ( Exp Op Exp ) ( Num Op Exp ) Num ( Num Op Exp )... Diese Suchverfahren, bei denen der Reihe nach alle möglichen Kodierungen durchgetestet werden bezeichnet man als Backtracking. 3.2.1 Termdarstellungen Wie verfährt der Compiler nun weiter mit dem Ableitungsbaum? Er wird weiter vereinfacht zu einem Term-Baum. Hierbei steht die Funktion bzw. der Operator jeweils oberhalb seiner Argumente. Nichtterminalsymbole kommen nicht mehr vor: Bsp.: 3 + sqrt(x 2 + 1) + 3 sqrt + 1 x 2 23

3 Programmierung Repräsentiert exakt die Termstruktur, aber enthält nicht die Informationen aus der (E)BNF. Beachte: Es kommen keine Klammern mehr vor! Termstruktur = Baumstruktur (Klammerung) Gibt es noch andere Darstellungen für Terme? Bisher: f(g(x,y),3) und Infixoperatoren (3+4). Ist die Stelligkeit aller Funktionen bekannt, ist es auch möglich die Klammern und Kommata ganz wegzulassen. Man erhält die klammerfreie Präfixnotation: f g x y 3 mit f und g 2-stellig + 3 4 x = +(3, (4,x)) = (3+(4 x)) +sqrt x 1 / 4 2 = (+(sqrt(x),1),/(4,2)) = (sqrt(x)+1) (4/2) Entsprechend gibt es auch die klammerfreie Postfixnotation, bei der alle Funktionsanwendungen nach den Argumenten folgen: Wozu sind diese Termdarstellungen gut? x y g 3 f 3 4 x + x sqrt 1 + 4 2 / Insbesondere die Postfixnotation kann gut zur automatischen Auswertung des Ausdrucks verwendet werden. Man verwendet hierzu eine sogenannte Stackmaschine. Ein Stack (oder Keller, Stapelspeicher) ist eine Struktur, in der beliebig viele Werte abgelegt und wieder herausgenommen werden können. Die Werte liegen hierbei übereinander, so dass immer nur oben auf die Werte zugegriffen werden kann. Operationen: push v, schiebt v auf den Stack pop, holt oberstes Element vom Stack Bsp.: push 3, push 4, pop, push 7, pop, pop 24

3.2 (Erweiterte) Backus-Naur-Form (BNF und EBNF) Es werden also der Reihe nach die Werte 4,7 und 3 vom Stack gepopt. Im folgenden werden wir Stacks auch horizontal notieren. Wichtig ist aber die Beachtung des LIFO-Prinzips (last-in-f irst-out) Einschub Es gibt auch das FIFO-Prinzip (f irst-in-f irst-out). Diese Struktur nennt man Queue (oder Schlange). Bei obigem Beispiel ergäbe sich: Also die Werte: 3,4,7 Später mehr zu Queues. Wie kann nun der Stack in der Stackmaschine verwendet werden, um den Wert eines Ausdrucks zu berechnen? Hierzu verwendet man am besten die Postfixnotation: Bsp.: (3+4) sqrt(2 2) Postfix: 3 4 + 2 2 sqrt Startkonfiguration: leerer Keller ε 3 4 + 2 2 sqrt 3 4 + 2 2 sqrt 3 4 + 2 2 sqrt 7 2 2 sqrt 7 2 2 sqrt 7 2 2 sqrt 7 4 sqrt 7 2 14 տ Ergebnis leere Eingabe Allgemein lässt sich dies mit folgenden Regeln beschreiben: Eine Konfiguration der Stackmaschine besteht aus einem Stack S und einer Eingabe p (einem Teil eines Postfixausdrucks). 25

3 Programmierung Die Startkonfiguration besteht aus einem leeren Stack und dem auszuwertenden Term in Postfixnotation p 0 : leerer Stack ց initialer, zu berechnender Term in Postfixnotation ւ ε p 0 Die Konfigurationsübergänge sind wie folgt definiert: S v p mit v ein Wert (z.b. Zahl, true, false) S v p S v 1...v n f p mit f eine n-stellige Funktion und f die Semantik von f S f(v 1...v n ) p Die Endkonfiguration hat die Form: v nur ein! Element auf Stack Das Ergebnis der Auswertung ist v leere Eingabe (Term komplett abgearbeitet) Weiteres Bsp.: if x>0 then x+1 else x 1 end +1 Term-Baum: + if then else 1 > x 0 + x 1 - x 1 Postfix-Notation: x 0 > x 1 + x 1 if then else 1 + Wir betrachten die Belegung: x = 40 26

3.2 (Erweiterte) Backus-Naur-Form (BNF und EBNF) Auswertung mit Stack-Maschine: 40 0 > 40 1 + 40 1 if then else 1 + 40 0 > 40 1 + 40 1 if then else1 + 40 0 > 40 1 + 40 1 if then else 1 + true 40 1 + 40 1 if then else 1 + true 40 1 + 40 1 if then else 1 + true 40 1 + 40 1 if then else 1 + true 41 40 1 if then else 1 + 2 true 41 40 1 if then else 1 + true 41 39 if then else 1 + 41 1 + 41 1 + 42 Beachte im Vergleich die Auswertung als Term: if 40 > 0 then 40+1 else 40 1 end + 1 if true then 40+1 else 40 1 end + 1 (40+1)+1 41+1 42 Die Berechnung von 40 1 wurde gespart. Das if then else kann auch schon ausgewertet werden, bevor das zweite und dritte Argument ausgewertet wurden, man sagt if then else ist nicht strikt im 2. und 3. Argument. Im Gegensatz zu z.b. +, welches strikt im 1. und 2. Argument ist. if then else ist auch strikt im 1. Argument. Da if then else entweder das zweite oder das dritte Argument liefert ist es sinnvoll, diese vor der if then else-auswertung nicht zu berechnen. Hierzu sind kompliziertere Stack-Maschinen notwendig Informatik-Studium Es können so auch Fehler verhindert werden: if b == 0 then 0 else a/b end verhindert Division durch Null und liefert in diesem Fall das Ergebnis 0. Zusatzinhalt, der für 5 ECTSler nicht relevant ist 3.2.2 Ausdrücke zur Implementierung einfacher Programme Nachdem wir nun gesehen haben, wie Ausdrücke formal definiert werden können, wollen wir uns die Verwendung von Ausdrücken zur Programmierung von Algorithmen genauer anschauen. Betrachte folgendes Problem: 27

3 Programmierung Gegeben: Radius r Aufgabe: Bestimme die Fläche eines Kreises mit Radius r Ausdruck: π r 2 Wie kann dieser Ausdruck nun für konkrete Radien ausgewertet werden? Bei gegebener Belegung von r können wir im Ausdruck r durch die Belegung ersetzen und dann ausrechnen: Bsp: r = 4 dann ergibt sich π 4 2 = π 16 50,264 r = 1 dann π 1 2 = π 1 3,1415... In Ruby können wir diese Funktion mit folgendem Ausdruck definieren: 3.14 (r 2) Dann können wir für gegebene Radien den Flächeninhalt eines Kreises berechnen: r = 1 3.14 (1 2) = 3.14 Bevor der Ausdruck ausgerechnet werden kann, müssen alle vorkommenden Variablen mit Werten belegt werden (Variablenbelegung hier: r=1). Erst danach kann der Ausdruck unter Berücksichtigung der Semantik der vordefinierten Funktionen zu einem Ergebniswert vereinfacht werden. Nun betrachten wir folgendes Problem: Gegeben: Zwei Zahlen n und m Aufgabe: Bestimme das Maximum von n und m Diese Aufgabe können wir mit den bisherigen Funktionen nicht lösen. Wir benötigen neue Funktionen: Erste Möglichkeit: max als genau die gesuchte Funktion, aber das geht so natürlich nicht immer. Alternative: Entwicklung des folgenden Algorithmus: Vergleiche n mit m Falls n < m ist, dann ist m das Maximum Sonst ist n das Maximum. 3.2.3 Boolesche Werte Welche neuen Funktionen benötigen wir hierfür? Zunächst den Vergleich <, aber was ist das Ergebnis von n < m? Eine Möglichkeit: 0 für nicht kleiner 1 für kleiner (So z.b. in C) Bessere Lösung: spezielle boolesche Werte: 28

3.2 (Erweiterte) Backus-Naur-Form (BNF und EBNF) f alse Ergebnis für nicht kleiner und true Ergebnis für kleiner Also: 3 < 4 true 4 < 4 false. Beachte, dass true und f alse zwar dem intuitiven wahr bzw. falsch entsprechen, aber dennoch Werte, wie 42 oder -15 sind. Genau wie 3+4 zu 7 reduziert wird, wird 3 < 4 zu true reduziert. Entsprechend stehen uns auch Funktionen <= (für ), > und >= (für ) zur Verfügung. Nun müssen wir aber noch eine Möglichkeit finden, wie wir das Falls...dann...sonst... umsetzen. Hierzu kann man eine Funktion if then else, welche drei Argumente benötigt verwenden. Ihre Semantik ist wie folgt definiert: Semantik von if then else if then else(true,e 1,e 2 ) = e 1 if then else(false,e 1,e 2 ) = e 2 Beachte, dass das erste Argument ein boolescher Wert sein muss, damit diese Funktion ausgewertet werden kann. D.h. es können hier z.b. Vergleiche verwendet werden. Damit können wir nun den Maximumsalgorithmus als Ausdruck implementieren: if then else (n>m,n,m) Werten wir diesen Ausdruck nun für unterschiedliche Variablenbelegungen aus, so ergibt sich: n = 7,m = 42 if then else(7 > 42,7,42) = if then else(false,7,42) = 42 und n = 42,m = 8 if then else(42 > 8,42,8) = if then else(true,42,8) = 42 In Ruby wird das if then else nicht als Applikation einer dreistelligen Funktion notiert, sondern in einer Mixfixnotation geschrieben: if e 0 then e 1 else e 2 end anstelle von if then else(e 0,e 1,e 2 ) In der Anwendung in unserem Ausdruck für die Maximumsberechnung also if n > m then n else m end Auch in Tabellenkalkulationsanwendungen verwendet man Ausdrücke zur Definition von Formeln. Auf Suns: staroffice Variablen = Feldnummern (z.b. A7, B16) Mit = können für Felder Formeln definiert werden, z.b. A7+B16 oder SQRT(A5) 29

3 Programmierung Hier wird das if-then-else auch tatsächlich als 3-stellige Funktion IF THEN ELSE oder WENN DANN SONST (in der deutschen Variante) notiert. Als weiteres Beispiel betrachten wir XOR (exklusives Oder). Gegeben: zwei boolesche Werte in x und y Gesucht: XOR(x, y) mit XOR x = true x = false y = true false true y = false true false Ruby-Ausdruck, der XOR berechnet: if x then if y then false else true end else if y then true else false end end oder mit if then else als dreistellige Funktion: if then else (x, if then else (y, false, true ), if then else (y, true, false )) Es gibt aber auch noch kompaktere Definitionen: if x==y then false else true end oder noch einfacher: x!=y Sprich die Funktionen!= und xor verhalten sich auf booleschen Werten identisch. 3.3 Anweisungen Ausdrücke können keine Wiederholungen von Vorgängen ausdrücken, welche aber notwendig sind, um viele Algorithmen zu implementieren. Beispiel: Fakultätsfunktion n! = 1 2 3... n Bsp: 5! = 1 2 3 4 5 = 120 2! = 1 2 = 2 D.h. der Ausdruck zur Fakultätsberechnung ist unterschiedlich groß für unterschiedliche Argumente n (wächst mit wachsendem n). 30

3.3 Anweisungen Wie kann das Problem aber algorithmisch gelöst werden? Eine Lösung ist die schrittweise Multiplikation der Faktoren. n n! 1 1 2 1 2 = 2 3 2 3 = 6 4 6 4 = 24 5 24 5 = 120.. Hierbei ist es nicht nötig sich alle vorherigen Ergebnisse zu merken. Das jeweils letzte Ergebnis reicht aus. Man kann von einem Ergebnis auf das nächste schließen: n! = (n 1)! n In Programmiersprachen können Werte in Variablen gespeichert werden, wodurch man sich in einem Programm Werte merken kann (Belegung von Variablen mit Werten). In imperativen Programmiersprachen können Variablenbelegungen durch Zuweisung geändert werden: Beispiele: x = 3 y = if x>z then x else z end y = y 2 Hierbei dürfen auf der rechten Seite der Zuweisung beliebige Ausdrücke stehen. Sind alle verwendeten Variablen belegt, so kann der Ausdruck zu einem Wert ausgewertet werden und die Variable wird mit diesem Wert belegt. Hierbei wird der Ausdruck stets mit der Belegung vor der Anweisung ausgewertet. y = y 2 < Wenn hier y mit 2 belegt ist, so ist < hier y mit 4 belegt. Nun ist es wichtig, Zuweisungen auch zusammenzuhängen, so dass sie hintereinander ausgeführt werden: fac = fac n; n = n+1; Anweisungen werden hierzu durch Semikolons abgeschlossen, damit klar ist, wo die nächste Anweisung beginnt. fac = 2; n = 3; fac = fac n; n = n+1; < 1 < 2 < 3 < 4 Programmausführung: 31

3 Programmierung Programmpunkt (PP) f ac n 1 2 Anfangsbelegung der Variablen f ac 2 3 Anfangsbelegung der Variablen n 3 6 neue Belegungen 4 4 ւ Im Folgenden werden wir die Programmpunkte immer als Kommentar an die Semikolons heften, also im Beispiel: fac = 2; #1 n = 3; #2 fac = fac n; #3 n = n+1; #4 Um nun nach und nach die Fakultät zu berechnen benötigen wir noch eine Wiederholungsmöglichkeit, auch Schleife genannt. 3.3.1 while-schleife: Bsp: n = 1; #1 while n<4 do n = n+1; #2 #3 Hierbei nennt man den Teil zwischen while und do die Bedingung und den Teil zwischen do und end den Rumpf der Schleife. Bedeutung: So lange die Bedingung gilt (also zu true ausgewertet wird), wird der Rumpf wiederholt. Die Bedingung wird vor Ausführung des Rumpfs überprüft. Programmausführung: PP n 1 1 2 2 2 3 3 32

3.3 Anweisungen Nun können wir die Fakultät berechnen: nmax = 4; #1 #zu berechnende Fakultaet n = 0; #2 #Zaehler fac = 1; #3 #(Teil )Ergebnis while n < nmax do n=n+1; #4 fac = fac n; #5 #6 puts(fac ); #7 Die Anweisung puts gibt das Ergebnis eines Ausdrucks auf dem Bildschirm aus, hier also die finale Belegung der Variablen fac. Programmausführung: Hierzu haben wir in unserem Programm alle Semikolons durchnummeriert (im Kommentar): PP nmax n fac 1 5 2 0 3 1 4 1 5 1 4 2 5 2 4 3 5 6 4 4 5 24 6 7 24 Das Ergebnis lautet also 24 und wird ausgegeben, was wir mit dem Pfeil andeuten. Programmausführung mit Ruby: Speichern als fac.rb Dann ausführen mit: ruby fac.rb Zuweisungen, Sequenzen und while-schleifen nennt man auch Anweisungen (Statements) und sie sind neben Ausdrücken eine weitere wichtige Struktur in imperativen Sprachen. Ihre Syntax können wir wie folgt formalisieren: Stm ::= Var = Exp ; Stm Stm while Exp do Stm end ; Beachte, dass sowohl in Zuweisungen, als auch in Bedingungen beliebige Ausdrücke stehen können (bei der Bedingung der while-schleife muss das Ergebnis natürlich ein boolescher Wert sein). Neben dem if-then-else auf Ausdrucksebene kann if-then-else auch auf Anweisungsebene verwendet werden. 33

3 Programmierung Stm ::=... if Exp then Stm else Stm end ; Hier ist es dann auch möglich den else-fall wegzulassen: Stm ::=... if Exp then Stm end ; Bsp.: Berechnung des Maximums von x und y: x =... ; y =... ; if x >= y then max = x; else max = y; Oder aber: x =... ; y =... ; max = x; if y>max then max = y; Betrachten wir noch einmal die Fakultätsberechnung. Anstatt die Faktoren hochzuzählen können wir auch runterzählen (mit Kommutativität von ): 1 2... n = n (n 1)... 2 1 Im Programm können wir diese Idee verwenden, um die Hilfsvariable n bzw. nmax zu sparen: n = 4; #1 fac = 1; #2 while n>0 do fac = fac n; #3 n=n 1; #4 #5 puts(fac ); #6 #zu berechnende Fakultaet PP n fac 1 4 2 1 3 4 4 3 3 12 4 2 3 24 4 1 3 24 4 0 5 6 Ausgabe: 24 Beachte: Zwischenergebnisse sind keine Fakultätsergebnisse mehr. Die Eingabevariable n wird verändert Wert geht verloren. Ungünstig, falls er nochmals benötigt wird! 34

3.3 Anweisungen 3.3.2 Größter gemeinsamer Teiler Um das Programmieren mit Schleifen weiter zu üben wollen wir uns die Bestimmung des größten gemeinsamen Teilers zweier gegebener Zahlen anschauen. Der größte gemeinsame Teiler zweier Zahlen wird z.b. beim Kürzen von Brüchen verwendet, wo man Nenner und Zähler durch ihren größten gemeinsamen Teiler dividiert. Def.: (ggt) Gegeben: a,b N Gesucht: ggt(a,b) = c N, so dass c teilt a ohne Rest und c teilt b ohne Rest und für alle weiteren Teiler d von a und b gilt c > d. Als Beispiel betrachten wir folgende Zahlen: 21 hat die Teiler: 1, 3, 7, 21 18 hat die Teiler: 1, 2, 3, 6, 9, 18 Somit ist der größte gemeinsame Teiler: ggt(18, 21) = 3 0 hat die Teiler: 1, 2, 3, 4, 5, 6, 7,... Somit gilt für alle a 0: ggt(a,0) = ggt(0,a) = a. ggt(0,0) ist nicht definiert, da alle Zahlen die 0 ohne Rest teilen und es somit keine größte Zahl gibt, die 0 teilt. Wie könnte nun eine mögliche Lösung dieses Problems aussehen? Bevor wir eine geschickte Lösung mit einem etwas geschickteren Algorithmus verwenden, lernen wir eine Methode kennen, die in vielen Fällen (allerdings oft nicht besonders geschickt) zum Ziel führt. 3.3.3 Aufzählen und Testen Ein großer Vorteil eines Computers gegenüber einem Menschen ist die Fähigkeit, viele Werte sehr schnell aufzählen und gewisse Eigenschaften für diese Werte testen zu können. Somit können viele Probleme, bei denen der Bereich der möglichen Lösungen endlich ist und aufgezählt werden kann, mit der Programmiertechnik Aufzählen und Testen gelöst werden. Dies ist auch für den ggt der Fall. Der ggt von zwei Zahlen n und m liegt sicherlich zwischen 1 und der kleineren der beiden Zahlen. Wir können also diese Werte der Reihe nach aufzählen und jeweils testen, ob die entsprechende Zahl beide gegebenen Zahlen ohne Rest teilt. Für die Überprüfung, ob eine Zahl eine andere ohne Rest teilt ist der Modulo-Operator sehr hilfreich, welcher den Rest einer ganzzahligen Division liefert. Falls a und b ganzzahlige Werte sind, so liefert/ die ganzzahlige Division und% den Rest der ganzzahligen Division. Bsp.: 12/9 1 12%9 3 16/3 5 16%3 1 35

3 Programmierung In der Vorlesung wurde zunächst vorgeschlagen, von 1 bis zum Minimum von a und b zu zählen, was zu folgendem Algorithmus führt: a = 12; #initiale Werte b = 9; max = if a<b then a else b i = 1; ggt = 1; while i<=max do if a%i==0 && b%i==0 then ggt=i ; i = i+1; puts(ggt); Beachte, dass der Ausdruck a%i==0 && b%i==0 wegen der Präzedenzen der verwendeten Operatoren, so geklammert ist: ((a%i)==0) && ((b%i)==0), d.h. &&(logisches Und) bindet schwächer als == bindet schwächer als %. Da wir den größten gemeinsamen Teiler suchen ist es für diese Aufgabe aber sinnvoller die Zahlen von oben nach unten aufzuzählen, da man dann bei der ersten Zahl, die beide gegebenen Zahlen ohne Rest teilt aufhören kann und den ggt ausgeben kann. Das Speichern des letzten Teilers wird überflüssig. Es ergibt sich folgendes, einfacheres ggt-programm: a = 12; #initiale Werte b = 9; ggt = if a<b then a else b while a%ggt!=0 b%ggt!=0 do ggt = ggt 1; puts(ggt); Beachte wieder, dass der Ausdruck a%ggt!=0 b%ggt!=0 wegen der Präzedenzen der verwendeten Operatoren, so geklammert ist: ((a%ggt)!=0) ((b%ggt)!=0), d.h. (logisches Oder) bindet schwächer als!= bindet schwächer als %. Problematisch sind nun noch die Randfälle a = 0 und/oder b = 0. Hier liefert das Programm einen Laufzeitfehler. Diese müssen nun noch explizit vor der Schleife abgefangen werden, was das Programm aber leider etwas aufbläht: a = 12; #initiale Werte b = 9; if a==0 then if b==0 then puts( ggt nicht definiert ); else puts(b); else if b==0 then puts(a); else ggt = if a<b then a else b while a%ggt!=0 b%ggt!=0 do 36

3.3 Anweisungen ggt = ggt 1; puts(ggt); Hier zeigt sich, dass es beim Testen der Programme auch wichtig ist, alle Randfälle systematisch zu überprüfen. 3.3.4 Euklidischer Algorithmus Bei großen Zahlen liefert die Aufzählen und Testen Methode die Lösung leider nicht mehr in akzeptabler Zeit. Wir betrachten deshalb eine effizientere Lösung, wie sie bereits ca. 300 v. Chr. von dem griechischen Mathematiker Euklid gefunden wurde. Gegeben zwei Strecken Bestimme eine Strecke, mit der man beide Strecken messen kann, d.h. die in beide Strecken ganz hineinpasst. Beide gegebenen Strecken sollen also Vielfache der gesuchten Strecke sein. Hier: Wie findet man so eine Strecke? Wenn beide Strecken gleich lang sind, passen sie natürlich in die jeweils andere hinein und es ist die gesuchte Strecke. Wenn nicht: ziehe die kürzere Strecke von der längeren ab. und suche nach einer Strecke, die in die kürzere Strecke und in die Strecke, die durch Abziehen der kürzeren von der längeren Strecke entsteht, hineinpasst. Da die gesuchte Strecke sowohl in die kürzere als auch in die längere Strecke hineinpassen soll, muss sie auch in die Differenz der beiden Strecken hineinpassen. Verfahre also weiter, bis beide Strecken gleich lang sind. Der Euklidische Algorithmus eignet sich also insbesondere auch zur Bestimmung des ggts. Wir arbeiten ähnlich wie bei der Idee mit den Strecken, allerdings enden wir nicht wenn beide Zahlen gleich sind, sondern erst, wenn eine der beiden 0 ist. Damit können wir den Fall, dass beide Zahlen 0 sind, separat abprüfen. Bsp.: ggt(15,10) 37

3 Programmierung 15 10 15 10 = 5 10 5 10 5 = 5 5 5 5 5 = 0 Also ggt(15,10) = 5. Bsp.: ggt(12,9) 12 9 12 9 = 3 9 3 9 3 = 6 6 3 6 3 = 3 3 3 3 3 = 0 Also ggt(12,9) = 3. Implementierung des Euklidischen Algorithmus nicht definiert, falls a = 0 und b = 0 a, falls b = 0 und a 0 ggt(a,b) = b, falls a = 0 und b 0 ggt(a,b a), falls a < b und b 0 ggt(a b,a), falls a b und a 0 ggt(12,9) = ggt(3,9) = ggt(3,6) = ggt(3,3) = ggt(0,3) = 3 Wie können wir diese mathematische Definition nun in Ruby realisieren? Zunächst fällt auf, dass die letzten beiden Fälle mit einer Iteration abgedeckt werden können. Die while-schleife sollte so lange wiederholt werden, bis a oder b den Wert Null erhält. Im Rumpf der Schleife mus entweder a = a b; oder b = b a; gerechnet werden, je nachdem, welcher Wert größer ist: a = 12; #1 b = 9; #2 while a!=0 && b!=0 do if a<b then b=b a; #3 else a=a b; #4 #5 #6 #initiale Werte Wenn wir also den Programmpunkt 6 erreichen, wissen wir, dass mindestens eine der Variablen a und b den Wert Null hat. Nun müssen wir nur noch die ersten drei Fälle unserer ggt-definition unterscheiden und haben das gesuchte Programm: 38

3.3 Anweisungen a = 12; #1 b = 9; #2 while a!=0 && b!=0 do if a<b then b=b a; #3 else a=a b; #4 #5 #6 #initiale Werte if a==0 then if b==0 then puts( ggt nicht definiert ); #7 else puts(b); #8 #9 else puts(a); #10 #11 Die Programmausführung sieht dann wie folgt aus: PP a b 1 12 2 9 4 3 5 3 6 5 3 3 5 4 0 5 6 8 3 9 11 Betrachte initiale Belegung: a = 3;b = 0; PP a b 1 3 2 0 6 10 3 11 Weitere sinnvolle Testfälle wären: a = 0, b = 3 und a = 0, b = 0. Außerdem können wir auch für größere Werte die Ausgaben unserer unterschiedlichen ggt-implementierungen vergleichen und uns so von der Korrektheit unser Programme überzeugen. Der Algorithmus kann auch noch weiter optimiert werden, wenn man die Subtraktion durch eine Division mit Restbildung ersetzt. Näheres hierzu in der Übung. 39

3 Programmierung 3.3.5 f or-schleife Bei vielen Iterationen weiß man genau, wie oft der Schleifen-Rumpf ausgeführt werden soll. Außerdem benötigt man oft eine Zählvariable, welche die Iterationen zählt und automatisch inkrementiert wird (i bei der ersten Version der Fakultät). Zu diesem Zweck kann man f or-schleifen verwenden: Stm ::=... for Var in Exp.. Exp Stm end Bsp.: PP n fac i 1 4 2 1 3 1 4 1 3 2 4 2 3 3 4 6 3 4 4 24 5 6 Ausgabe: 24 Die Zählvariable wird vor der nächsten Iteration hochgezählt, falls Endwert noch nicht erreicht wurde. Beachte: Eine f or-schleife wird immer beendet (terminiert immer), im Gegensatz zu while-schleifen: Bsp.: while true do... end terminiert nicht oder wenn vergessen wird, die Zählvariable zu verändern: while n>0 do fac = fac n; #n=n 1; vergessen kann die while-schleife ebenfalls nicht terminieren. Dies ist bei der f or-schleife nicht möglich. Selbst wenn wir im Rumpf die Zählvariable verändern (was man aber auf keinen Fall machen sollte, da es zu unverständlichen Programmen führt!), wird der 40