Ich begrüße euch herzlich zur heutigen Vorlesung der Grächner
Organisation.
Mein Name ist Tamim Asfour.
Professor Henkel hat mich kurz vorgestellt, obwohl ich nicht da war
bei der ersten Vorlesung.
Ich werde hauptsächlich die Übungen zu dieser Vorlesung betreuen, aber
auch ab und zu mal hier die Vorlesung zusammen mit Professor Henkel
halten.
Geht es ein bisschen leiser?
Ich kann natürlich das Mikro hier ein bisschen lauter machen, aber das
wird unangenehm, glaube ich, für viele.
Bevor ich hier weitermache mit dem Stoff, würde ich mich gerne kurz
vorstellen, und zwar auch meinen Lehrstuhl.
Ich bin vom Institut für Anthropomatik und Robotik und mein Lehrstuhl
für humanoide Robotiksysteme beschäftigt sich mit der Entwicklung von
humanoiden Robotern, die uns in vieler Hinsicht behilflich sein
werden, jetzt, aber auch in der Zukunft.
Speziell beschäftigen wir uns mit der Fragestellung, wie kann man
diese Systeme von der mechanoinformatischen Seite entwickeln, wie kann
man sie beherrschen von der Mechatronikseite, aber auch von der
Softwareseite und wie kann man kognitive Fähigkeiten in solchen
Systemen implementieren.
Die zwei Hauptforschungsfragestellungen, die uns beschäftigen, neben
dem Engineering von diesen Systemen, ist zunächst mal, wie diese
Systeme lernen, Objekte zu greifen und zu manipulieren.
Auf der einen Seite, wenn sie uns unterstützen sollen, dann sollen sie
in der Lage sein, vernünftige Aufgaben zu machen.
Vernünftige Aufgaben bedeutet, sie müssen Objekte greifen, Objekte
erkennen, mit Objekten bestimmte Aktionen ausführen.
Und zum Zweiten sollen sie auch in der Lage sein, durch Beobachtung
des Menschen zu lernen.
Man sieht hier zwei Beispiele.
Im ersten Beispiel ist das, was der Roboter aktuell ausführen kann in
unserem Labor.
Unser Labor ist eine Küchenumgebung, wie man hier sieht.
Das heißt, der Roboter ist in der Lage, Objekte visuell zu erkennen,
zu greifen, die Geschirrspülmaschine aufzumachen, zu beladen.
Man sieht hier eine Vielzahl von Fähigkeiten, die implementiert sind.
Natürlich die Reichen von der Softwarearchitektur und der Hardware
dieser Systeme, bis hin zur Bildverarbeitung, aber auch zur
natürlichen Interaktion und Kommunikation, also mithilfe von Sprache.
Fragestellungen der Bewegungsplanung, der Regelung und Steuerung
dieser Systeme, basierend auf multimodaler sensorischer Informationen.
Und das zweite Beispiel, was ich hier mitgebracht habe, ist das
Beispiel, wie diese Roboter, da sie ja humanoid sind, also zwei Arme
und zwei Hände haben, wie sie zweihändige Manipulationsaufgaben
ausführen, als zum Beispiel das Einschenken von Saft, aber auch das
Greifen von großen Objekten, wie ein Wok jetzt hier in der Küche, und
das Transportieren von einer Seite oder von einem Platz in der Küche
zu einem anderen Platz.
Ja, das sieht natürlich ein bisschen langsam aus.
Das sind Aktivitäten, die natürlich kinderleicht aussehen oder
kinderleicht sind für uns Menschen, aber die stellen immer noch
technische Herausforderungen dar, vor allem für Systeme, die in
dynamischen Umgebungen agieren sollen.
Das ist jetzt keine Umgebung, die fest ist auch.
Es ändert sich natürlich alles bei jedem Wetter.
Wenn man an die Bildverarbeitungsalgorithmen denkt, die müssen
natürlich robust genug sein, um bei sonnigem Wetter, aber auch bei
einem regnerischen Wetter oder wenn es schneit auch, also wenn es
draußen dunkel ist, sollen auch trotzdem die Algorithmen
funktionieren.
Zum zweiten, was ich hier erwähnt habe, die Fähigkeit, aus Beobachtung
des Menschen zu lernen.
Man sieht hier ein Beispiel, wie der Mensch dem Roboter demonstriert,
wie ein Tisch zu wischen ist.
Und aus dieser Demonstration extrahiert eigentlich der Roboter die
wesentlichen Parameter.
Ja, der Mensch fängt an jetzt hier den Tisch zu wischen und wir wollen
ja nicht die Bewegungen kopieren, also eins zu eins übertragen,
sondern wir wollen Fähigkeiten lernen.
Wir wollen die Fähigkeit des Wischens lernen und diese Fähigkeit des
Wischen natürlich auf unterschiedliche Umgebungen und Situationen
adaptieren.
Das heißt, nachdem der Roboter hier jetzt die wesentlichen Parameter
gelernt hat, zum Beispiel die Form der Bewegung, aber auch die
Frequenz der Bewegung, damit man schneller und langsamer wischen kann,
beziehungsweise die Amplitude der Wischbewegung, kann der Roboter das
natürlich abbilden oder reproduzieren.
Wichtig ist beim Wischen, dass der Schwamm natürlich Kontakt mit der
Umgebung hat, ansonsten bleibt der Tisch die ganze Zeit schmutzig.
Das heißt, hier während der Roboter die Bewegung zum ersten Mal
ausführt, lernt er auch die Kraftprofile durch diese Interaktion mit
der Oberfläche, so dass er später in der Lage ist, natürlich nicht nur
flache Oberflächen, wie man hier sieht, zu wischen, sondern auch
konkrete und konvexe Oberflächen, weil letztendlich soll er ja auch
die Badewanne bei uns zu Hause wischen oder den Waschbecken und die
sind ja nicht flach.
Okay, das ist die erste Generation unserer Roboter, eigentlich die
dritte, die vierte Generation, das sind humanoide Roboter, die auf
zwei Beinen laufen sollen, nicht weil wir die Aufgaben in dieser
Küchenumgebung nicht mit einem mobilen Untersatz erledigen können,
sondern weil wir mehr verstehen wollen über die Koordination zwischen
Laufen und Manipulation beziehungsweise eigentlich möchten wir mehr
verstehen über das Fallen, damit wir assistive Systeme bauen können,
die zum Beispiel das Fallen bei Menschen verhindern könnten.
Wir arbeiten gerade daran, das heißt praktisch die Mechano-Informatik
dieser Systeme hier fertigzustellen.
Das sind keine einfachen Systeme, 63 Bewegungsachsen, über 230
Sensoren, 76 Mikrocontroller.
Das heißt, das was wir hier in dieser Vorlesung lernen, in einer
Rechnerorganisation, ist natürlich ein wesentlicher Bestandteil von
der Mechano-Informatik dieser Roboter, aber natürlich nicht beschränkt
auf Roboter-Systeme.
Die fünfte Generation sind sogenannte anziehbare humanoide Roboter,
das heißt nicht Roboter, die alleine in der Küche was für uns
erledigen oder im Haushalt oder bei der Arbeit, sondern Roboter, die
man tatsächlich anziehen kann, um motorische Fähigkeiten, aber auch
sensorische Fähigkeiten zu erweitern beziehungsweise bei Menschen mit
motorischen oder sensorischen Einschränkungen diese Fähigkeiten zu
kompensieren.
Okay, wir bzw.
mein Lehrstuhl H2T ist direkt gegenüber hier, der Haupteinfahrt der
Uni oder des KIT.
Also da ist Audimax hier, wie man sieht, da ist die Mensa und wir sind
hier im Gebäude, also die ehemalige Kinderklinik eigentlich.
Und ich bin im Raum 017 für diejenigen, die irgendwelche Fragen haben,
natürlich zur Rechnerorganisation, aber auch zum Thema Robotik.
Und wir haben das letzte Mal mit Kapitel 2 angefangen und da hat sich
die Frage gestellt, was sind eigentlich die Anforderungen höherer
Programmiersprachen an die Hardware?
Weil klar, wir möchten ja Hardware bauen, die Anwendungen des
Benutzers auf eine möglichst effiziente und performante Art und Weise
realisieren.
Deshalb stellen wir uns die Frage, was sind die Anforderungen, was
muss so eine Hardware realisieren, damit die Softwarekonzepte, die wir
kennen, halt übertragbar sind.
Und wir haben damit angefangen mit einer kleinen Einführung, wie kommt
man von einem Quellcode einer Problemstellung, geschrieben in einer
beliebigen Programmiersprache, zu einem ausführbaren Programm.
Und da haben wir gesehen, dass wir hier unser Code zum Beispiel in C++
oder C schreiben, dass wir einen Compiler haben, der diesen Code in
ein Object Code zunächst mal umsetzt, wo noch ungelöste Referenzen auf
Speicherbereiche, auf externe Bibliotheken usw.
enthalten sind.
Diese ungelösten Referenzen, die werden mithilfe von einem Linker oder
von einem Binder auf Deutsch aufgelöst, so dass man zu einem
ausführbaren Programm hier kommt.
Das kennt ihr wahrscheinlich alle, also vor allem die Leute, die auch
BSE mal gemacht haben, die wissen ja all diese Begriffe, wie das hier
eigentlich vonstatten geht.
Speziell in dieser Vorlesung ist, dass wir nach der zweiten, dritten
Woche eigentlich nicht, wir werden natürlich in den ersten zwei Wochen
in C ein paar Aufgaben programmieren, oder ihr werdet das machen.
Speziell aber ist, dass wir dann dazu übergehen werden, unsere
Programme in Assembler zu schreiben.
Also wenn wir an dieser Folie mit den Abstraktionsebenen nachdenken
oder denken oder uns erinnern, dann war für uns hier C ganz oben und
dann unter C kommt die Assemblersprache bis hin zu Maschinencode und
Mikroprogramme, die letztendlich Befehle auf der Hardware ausführen.
Das heißt, wir werden unsere Programme in Assembler schreiben.
Wir haben keinen Compiler, der Programme in einer Hochsprache auf
einen Objektcode abbildet.
Diese Programme, die werden dann von einem, oder diese Files oder
dieser Code, wird von einem Programm namens Assembler übersetzt in
diesen Objektcode, sodass wir am Ende unser ausführbares Programm auf
die gleiche Art und Weise auch erhalten.
So, das ist der Ablauf, was das letzte Mal auch erklärt wurde.
Das ist ja ein Beispiel und speziell jetzt zu C.
Wer von euch kann C programmieren oder hat C programmiert?
Ein paar, doch.
Wer kann überhaupt programmieren?
Okay, also alle.
Oder stellen wir die Frage, wer kann nicht programmieren?
Eine.
Zwei ehrliche Menschen haben wir hier mindestens.
Mindestens, ja.
Okay, also, das heißt, wenn man sich damit beschäftigt oder wenn man
eine Programmiersprache lernen will, dann stellt sich die Frage, was
für Datentypen habe ich in dieser Sprache,
welche Kontrollstrukturen habe ich, worauf muss ich achten und so
weiter und so fort.
Also, wir haben das letzte Mal gesehen, dass wir hier in C vier
Grunddatentypen haben, Charakter, Integer, Float und Double und dass
die Bereiche dieser Grundtypen natürlich rechnerabhängig sind, je
nachdem, wenn man mit einem 8-Bit-System arbeitet oder 64-Bit-System
arbeitet, dann sind natürlich die Wertebereiche hier der
Grunddatentypen unterschiedlich.
Und wir haben gesehen, dass es hier alle Operationen gibt, die man
sich so wünscht, wenn man Programme schreibt, natürlich muss man
Addieren, Subtrahieren, Modulo, Divisionen ausführen können.
Ganz wichtig für uns in dieser Vorlesung sind Bit-Operatoren, das
heißt Operatoren, mit denen man Bit-Operationen ausführen kann.
Das heißt zum Beispiel das bitweise Negieren von einer Zahl, die im
Dual angegeben ist, oder das Links- und Rechtsverschieben von Zahlen
im Dualsystem.
Das sind zwei Operationen, die sehr wichtig sind im Zusammenhang mit
der Realisierung von Multiplikations- und Divisionsalgorithmen, weil
ein Verschieben nach links bedeutet eigentlich eine Multiplikation.
Im Dualsystem eine Verschiebung nach links um eine Stelle bedeutet
eine Multiplikation mit zwei und eine Verschiebung nach links bedeutet
eine Division durch zwei.
Dann natürlich alle logischen Operatoren, die wir kennen.
Also wie kann man zwei Operanten X und Y mithilfe von dem logischen
Und hier miteinander verknüpfen, beziehungsweise mithilfe von XOR,
also der Antivalence, oder mithilfe der OR-Verknüpfung.
Dann natürlich für Gleis-Operationen, die sehr wichtig sind zur
Realisierung von Schleifen, weil man in den Bedienungen eigentlich,
Abbruchbedienungen von Schleifen, immer solche Angaben machen muss,
wenn zum Beispiel die Zählvariable kleiner gleich 100, dann soll
beispielsweise eine bestimmte Schleife verlassen werden.
Das heißt, großer, großer gleich, kleiner, kleiner gleich, gleich und
das Ungleich hier.
Und als weitere Beispiele sind hier die Spezialoperatoren, mit denen
man zum Beispiel den Wert einer Variable dekrementiert,
beziehungsweise inkrementiert und zwar entweder bevor man den Wert
einer Variable verwendet, beziehungsweise nachdem man den Wert dieser
Variable verwendet.
Das unterscheidet man hier anhand von diesem Postfix, das heißt man
verwendet hier den Wert der Variable X und anschließend wird der Wert
von X inkrementiert, also um 1 erhöht.
Im zweiten Beispiel verwendet man den Wert zunächst mal von X und
anschließend wird X dekrementiert, also um 1 erniedrigt.
Im Gegensatz dazu bedeutet dieses plus plus X, also Prefix hier, dass
man zunächst mal den Wert von X um 1 erhöht und anschließend die
betreffende Operation mit X ausführt und gleich hier natürlich
dekrementieren und anschließend die Operation mit X ausführt.
Es gibt auch kombinierte Operatoren, also zum Beispiel plusgleich oder
minusgleich, malgleich, durchgleich und modulogleich.
Das bedeutet eigentlich nichts anderes, dass der Wert hier, der neue
Wert von X, nichts anderes ist als der alte Wert von X plus Y hier,
der neue Wert ist der alte Wert von X minus Y, mal Y, durch Y und so
weiter und so fort.
Natürlich gilt das Gleiche auch für diese Bit-Operationen oder für die
logischen Operationen, die wir auch vorher kennengelernt haben.
Ein ganz spezieller Operator ist dieser Vergleichsoperator, das heißt
Fragezeichen -Doppelpunkt, wo man zwei Statements hier oder Ausdrücke
hat, Ausdruck 1 und Ausdruck 2.
Und wenn das zutrifft hier zum Beispiel, der erste Ausdruck ist A B
und der zweite Ausdruck ist A.
Das heißt, wenn A B ist, dann soll das Ergebnis A sein.
Das bedeutet, mit Hilfe von dieser Anweisung hier kann man nichts
anderes als die größere Zahl der beiden Zellen A und B realisieren.
Also wenn hier zum Beispiel im Falle einer logischen Variable, wenn A
wahr ist, dann ist X gleich A, ansonsten ist X gleich B.
Das ist ein sehr wichtiges und interessantes Beispiel, was wir sehr
begegnen werden in der Vorlesung, weil gerade dieser Ausdruck hier ist
nichts anderes als die Realisierung eines Multiplexer.
Multiplexer, das sind Bausteine, die wir in Digitaltechnik und
Entwurfsverfahren kennenlernen werden.
Die haben mehrere Eingänge, die haben eine Steuervariable und einen
einzigen Ausgang.
Und in Abhängigkeit von dieser Steuervariable A wird eins der Eingänge
selektiert.
Und hier anhand von dem Bild, wir werden die Details in der Vorlesung
Digitaltechnik kennenlernen, anhand von dem Bild kann man ablesen,
wenn A gleich 0 ist, dann ist X gleich C und wenn A gleich 1 ist, dann
ist X gleich B.
Das heißt eigentlich mithilfe von diesen Ausdrücken kann man auch
diese Hardware, Bauelemente und Schaltungen auch formal beschreiben.
Also das ist ja ein bedingter Sprung, entspricht der logischen
Funktion eines Multiplexer.
Ganz wichtig, wenn man Programme schreibt, ist natürlich die Frage der
Prioritäten.
Das heißt, welcher Operator hat ja eine höhere Priorität bei der
Auswertung von einem bestimmten Ausdruck.
Und das sind hier die Prioritäten der Operatoren beziehungsweise auch
die Assoziativität, ob die Auswertung von links nach rechts oder von
rechts nach links erfolgen soll.
Und man sieht hier, dass zum Beispiel Funktionsaufrufe die höchste
Priorität haben.
Indizierung in einem Array, anschließend dann Elementzugriffe mithilfe
von Zeigern, auch das Vorzeichen usw.
Ich werde jetzt nicht auf die einzelnen Details eingehen.
Ich glaube, das ist den meisten von euch hier auch bekannt und
selbstverständlich.
Wenn man Zweifel hat an der einen oder anderen Stelle, dann kann man
hier in diese Tabellen reingucken.
Man sieht hier, dass zum Beispiel Gleich und Ungleich sind ziemlich
weit hinten in der Priorität und der Kommaoperator natürlich ganz am
Ende steht.
Gibt es irgendwelche Fragen zu dem ganzen Zeug hier, zu diesen
Tabellen?
Die muss man ja angucken, wenn einer von euch Probleme hat beim
Ablesen oder Interpretieren von dem, was auf diesen Folien in den
Tabellen steht, dann soll sich jetzt melden.
Ansonsten machen wir weiter.
Gibt es jemand?
Nein.
Dann gibt es natürlich Kontrollstrukturen.
Kontrollstrukturen definieren natürlich die Reihenfolge, in der
bestimmte Operationen ausgeführt werden.
Also nicht auf Operatorebene, sondern auf Block-Ebene in einem
Programm.
Wahrscheinlich kennen die meisten von euch die IF-Anweisung.
Das ist nochmal hier kinderleicht dargestellt.
Das heißt, man hat ja eine Bedienung IF und wenn diese Bedienung wahr
ist, dann sollen bestimmte Aktionen ausgeführt werden.
Ich nenne sie jetzt die IF-Aktionen in blau.
Wenn diese IF-Bedienung nicht erfüllt ist oder nicht wahr ist, dann
sollen die weiteren Aktionen hier ausgeführt werden.
In Form von einem Flussdiagramm sieht man hier die Verzweigung bei der
Auswertung von dieser Bedienung.
Ist sie wahr, dann werden diese Aktionen ausgeführt.
Ist sie nicht wahr, dann macht man natürlich an einer Stelle hinter
den IF-Aktionen weiter mit den weiteren Aktionen.
Dann gibt es natürlich die IF-ELS-Anweisung, wo man zwei Blöcke hat.
Einmal die IF- und einmal die ELS-Anweisungen.
Die stehen, wie man sieht hier im Flussdiagramm, parallel zueinander.
Das heißt, ich führe entweder die Anweisungen oder die Anweisungen und
mache anschließend in beiden Fällen die weiteren Aktionen durch.
Natürlich im Gegensatz dazu, die hier werden überhaupt nicht
ausgeführt, wenn die Bedienung hier nicht stimmt.
Aber das ist, glaube ich, einfach.
Dann gibt es die mehrfache Verzweigung oder die mehrstufige
Verzweigung, zum Beispiel mithilfe der SWITCH-Anweisung, wo man hier
einen Ausdruck hat, der einen bestimmten Wert hat.
Und je nachdem, welchen Wert dieser Ausdruck hat, springt man
sozusagen in eins von diesen Fällen hier, also K, S, A, B, C und führt
die entsprechenden Anweisungen im ersten Fall oder im zweiten Fall
oder im dritten Fall und so weiter und so fort.
Die While-Schleife ist eine weitere Kontrollstruktur.
Das heißt, man hat eine Bedienung und bestimmte Aktionen werden hier
ausgeführt, solange diese Bedienung wahr ist.
Wenn die Bedienung nicht mehr wahr ist, dann macht man weiter hier mit
diesen Anweisungen.
Das heißt, hier sieht man, die Bedienung ist hier wahr, dann wird die
Schleife, also damit realisiert man natürlich diese Schleife hier,
ansonsten geht man weiter und macht man etwas anderes.
Dann gibt es die Vorschleifen, wo man so eine Initialisierung der
Schleife hat, eine Bedienung dieser Schleife und eine
Reinitialisierung hat.
Also zum Beispiel bei der Addition der Elemente eines Arrays, nehmen
wir mal an, da fängt man hier mit einem bestimmten Index an, wenn
dieser Index kleiner ist als die Dimension von dem Array und der Index
wird nach jedem Durchlauf der Schleife um 1 inkrementiert.
Beispielsweise hier wird die Additionsoperation ausgeführt und solange
die Bedienung wahr ist, wird die Schleife durchlaufen, ansonsten macht
man weiter mit den weiteren Aktionen.
Die Do-While-Anweisung ist auch ähnlich zu der While-Anweisung mit dem
Unterschied, dass wir einen Do-Block hier haben und dieser Do-Block
wird solange gemacht, also solange diese Bedienung hier wahr ist und
wenn die Bedienung bei einem bestimmten Durchlauf nicht mehr richtig
ist, dann macht man weiter hier mit den weiteren Aktionen.
So, das war ziemlich einfach, aber man braucht nicht viel darüber zu
reden, die Beispiele habt ihr wahrscheinlich mehrfach gesehen, oder?
So, jetzt wie sieht der Aufbau eines Programms in C aus, wie der
Aufbau eines Programms wahrscheinlich in jeder anderen
Programmiersprache?
Geht es ein bisschen leiser?
Okay, wir warten bis alle fertig sind.
So, der Aufbau, da hat man natürlich den Quellcode, der steht in
mehreren Modulen oder in mehreren Files, also hier zum Beispiel Modul
1.2.n und dann hat man mehrere Header-Files, entsprechend auch hier
benannt mit Modul 1, Modul 2.h Normalerweise sieht ein C-Programm so
aus, dass man zunächst mal bestimmte Includes von bestimmten
Bibliotheken, zum Beispiel Standard-IO-Bibliothek, stdio.h, aber auch
eigene Module, also einige H-Module.
Man definiert hier bestimmte Makros zum Beispiel, das sind Anweisungen
an den Pre-Prozessor, die notwendig sind bei der Erstellung von dem
Object -Code oder zu Text-Ersetzungen im Programmcode.
Dann hat man globale Deklarationen und Definitionen von Variablen, wo
man Variablen deklarieren kann, zum Beispiel hier Integer bzw.
Variablen definieren kann, wo man sowohl Platz für die Variablen
reserviert, als auch eine Wertzuordnung macht.
Funktionsprototypen in C, die werden auch hier auf die Art und Weise
definiert.
Das heißt hier, das ist ein Unterprogramm sozusagen in C, also eine
Funktion nennt man das.
Die liefert einen Integer-Wert, die hat eine Argumentenliste, in dem
Fall nur ein Argument, was auch Integer ist und muss auch als Teil von
dieser globalen Deklaration und Definitionsbereich sein.
So sieht beispielsweise diese Funktion aus, das ist die Funktion, die
die Fakultät berechnet, die Fakultät einer Integer-Zahl und das
Hauptprogramm muss immer eine Marquee-Mean haben und in diesem Mean
kann man natürlich auf diese Variablen bzw.
auf diese Funktionen, die man definiert hat, zugreifen.
Jedes Programm enthält eine Funktion, die heißt Mean-Funktion und die
Unterprogramme werden in C in Form von Funktionen definiert, die
Parameterlisten und Ergebnistypen haben, also Parameterliste hier von
dieser Fakultätsfunktion ist nur die Zahl, für die wir die Fakultät
berechnen wollen.
Und der Wert ist dann wiederum, also der Rückgabewert von dieser
Funktion ist wiederum ein Integer, was hier auch eingegeben ist und
die Abarbeitung dieses Programms fängt natürlich immer hier mit Mean
an und innerhalb von Mean können weitere Funktionen aufgerufen werden.
Die Funktionen werden global definiert, also alle Funktionen, auf die
man zugreift, werden global definiert und rekursive Funktionsaufrufe
sind auch zulässig.
Als Beispiel hier die Fakultätsberechnung.
Wir wissen, um die Fakultät von n zu berechnen, müssen wir n mal n-1
mal n-2 berechnen.
Das heißt, das ist eigentlich nichts anderes als n multipliziert mit
der Fakultät mit dem dekrementierten Argument in der Funktion
Fakultät.
Das heißt, solche rekursiven Funktionen sind auch hier zulässig.
Die Parameterübergabe entscheidet man hier zwischen zwei Typen.
Einmal sogenannte Reference-by-Value oder Call-by-Value.
Das ist so der Normalfall in C.
Was bedeutet das eigentlich?
Das bedeutet, es wird immer eine Kopie von dem aktuellen Parameter an
die Funktion übergeben.
Das heißt, das richtige Parameter, also wenn ich ein a habe, ich will
die Fakultät von 100 berechnen und ich habe a definiert irgendwo in
meinem Programm und die Funktion Fakultät greift jetzt auf dieses
Argument 100, dann wird eine Kopie von diesem aktuellen Parameter an
die Funktion Fakultät übergeben.
Die Funktionen können natürlich den Wert dieser Kopien ändern.
Wir haben es ja gesehen bei der rekursiven Berechnung, dass diese 100
ständig dekrementiert wird bei jedem weiteren Aufruf dieser Funktion.
Aber ohne, dass man irgendwelche Auswirkungen auf das ursprüngliche
Parameter, die 100, die wir berechnen wollen.
Das heißt, diese Änderungen sind natürlich lokal in der Funktion und
beeinflussen den Wert der Originalvariablen nicht.
Nun wissen wir, dass es auch Call-by-Reference gibt.
In C ist dieses Call-by-Reference nur indirekt.
Es wird realisiert über sogenannte Zeiger oder Pointer, die wir gleich
kennenlernen werden.
Der Übergabeparameter ist eine Variable und die aufgerufene Funktion
enthält die Speicheradresse dieser Variable.
Das heißt, wenn die Funktion den Wert des Parameters verändert, ändert
sie natürlich den Inhalt der zugehörigen Speicherzelle und der Wert
der Variablen beim Aufrufe der Funktion ändert sich auch.
Aber das kennt ihr wahrscheinlich, oder?
Das habt ihr irgendwo gelernt.
Grundbegriff wieder Informatik, Softwareentwicklung und andere
Vorlesungen.
Dann vielleicht ein paar Worte zu globalen und lokalen Variablen.
Globale Variablen sind im gesamten Programm einschließlich aller
Unterprogramme bekannt.
Das sollte man normalerweise vermeiden.
Man sieht hier in dem Beispiel, ich habe die globale Integer-Variable
hier definiert und einen Wert zugewiesen, gleich Null und der Wert
hier oder diese Variable ist im gesamten Code oder im gesamten
Programm hier bekannt.
Lokale Variablen sind nur innerhalb einer Funktion oder eines
bestimmten Blocks definiert.
Das heißt, wenn ich hier meine Funktion void Main habe und ich
definiere hier die lokale Variable 1, dann ist diese lokale Variable
gültig in dem ganzen Main-Block.
Wenn ich aber hier beispielsweise diesen Block hier habe und nochmal
eine lokale Variable definiere, dann ist diese Variable nur in diesem
grünen Block hier definiert.
Das heißt, wenn ich hier ein Printf habe, dann werde ich hier
natürlich für die globale Variable immer 0 bekommen und für die lokale
Variable 1.
Aber wenn dieses Printf an der Stelle hier erfolgt, also bei grün,
dann würde ich als Ergebnis 0 und 2 bekommen, weil die lokale Variable
hier den Wert 2 hat.
Dann einige Worte zu den Speicherklassen bei C.
Diese Speicherklassen von Variablen definieren die Lebensdauer und den
Gültigkeitsbereich oder auch Sichtbarkeitsbereich von Variablen und
von Funktionen.
Die haben eigentlich viel damit zu tun, also wenn man jetzt nicht nur
softwaretechnisch denkt, sondern ein bisschen an den Aufbau von dem
Rechner denkt, dann hat man natürlich irgendwo einen Hauptspeicher und
man hat hier einen Prozessor und auf diesem Prozessor hat man
natürlich so ein bisschen Speicher in Form von Cache-Speicher bzw.
auch in Form von Register.
Und viele von diesen Variablen, die man kennt, also mit den
unterschiedlichen Speicherklassen, die haben natürlich mit dieser
Speicherhierarchie, die man im Rechner vorfindet, zu tun.
Zunächst einmal haben wir die Speicherklasse Auto, das heißt, das ist
für lokale Variablen eingesetzt und Auto-Objekte gelten nur so lange
wie der Bereich, in dem sie definiert wurden.
Register, also die Speicherklasse Register, das deutet sofort darauf
hin, dass das sind Variablen, die tatsächlich in dem Registersatz
eines Prozessors gespeichert werden müssen.
Das ist der Mikroprozessor hier, der hat einen gewissen Registersatz,
also ein paar Register, auf die der Prozessor extrem schnell zugreifen
kann, also wesentlich schneller als auf den Hauptspeicher.
Und wenn man eine solche Variable als Register, also der
Speicherklasse Register definiert, dann ist es eine lokale Variable,
auf die man immer sehr schnell zugreifen will.
Zum Beispiel Zählvariablen bei Zähler.
Statische Variablen werden statisch Speicherplatz zugewiesen im
Hauptspeicher, das heißt, die bleiben ja an der gleichen Stelle, die
werden nicht dynamisch irgendwo an einem anderen Bereich oder an einem
anderen Platz hier zugewiesen.
Und extern definiert eine globale Variable, die in allen
Programmmodulen sichtbar ist.
Gut, das ist so der Vollständigkeit halber zu den unterschiedlichen
Speicherklassen von Variablen, die man in C findet.
Ein ganz wichtiges Werkzeug eigentlich von C, und wenn man das Wort C
-Programmieren hört, dann kommt sofort immer der Begriff Zeiger und
Pointer vielleicht, zumindest bei mir, ich weiß nicht, bei euch, wie
das so ist.
Und zwar, das ist eine Möglichkeit oder ein Werkzeug der C-Sprache,
mit dem man extrem schnell oder sehr effizient programmieren kann,
weil man dadurch Kopieroperationen vermeidet.
Man nennt das auch Pointer-Arithmetik, das heißt, man greift direkt
hier auf Elemente in strukturierten Daten, also in Tabellen zum
Beispiel, Arrays.
Und natürlich, wenn man das Ganze massiv einsetzt, dann sieht der Code
ziemlich unleserisch aus, beziehungsweise nur für gute Nerven oder für
starke Nerven eigentlich.
Ansonsten ist es nicht so ablesbar oder nicht so schnell
interpretierbar, was gerade da passiert.
Wir werden das hier nicht übertreiben auf den Übungsblättern, aber ihr
werdet euch ein bisschen damit rumschlagen müssen.
Das macht natürlich auch das Programmieren in C ein bisschen
fehlerauffällig.
Wer hat mit Zeiger programmiert eigentlich, oder wer weiß, worüber ich
rede?
Also doch nicht so viele, ja?
Und, ist es gut, mit Zeiger zu programmieren?
Okay, was sind das eigentlich?
Ein Zeiger, beziehungsweise in Englisch ein Pointer, enthält eine
Adresse, die auf Daten verweist.
Das heißt, die werden auf die Art und Weise definiert.
Das heißt, man definiert hier einen Zeiger auf einen Integer.
Das heißt, hier auch wiederum Q ist ein Zeiger auf einen Integer und
die Variable P und Q ist dann vom Typ Zeiger auf Integer.
Während der ganze hier Sternblock, also dieser Oneri-Operator Stern,
Stern P und Stern Q, das sind dann vom Typ Integer.
Und was macht man hier?
Man kann natürlich hier, wie wir das vorher gesehen haben, Variable A
definieren, Wert zuweisen, Variable B und kann man jetzt Operationen
ausführen.
Man kann zum Beispiel sagen, dass P, P ist ja ein Zeiger auf ein
Integer, aber wir haben ja noch nicht gesagt, auf welches Integer P
zeigt.
Mithilfe von diesem Oneri-Operator, mit diesem kaufmännischen und,
können wir sagen, dass P eigentlich die Adresse von A enthält.
Also P enthält die Adresse von A und wenn wir das hier haben, dann
wissen wir, dass Stern P nichts anderes ist als der Inhalt der Adresse
von A und mit dem können wir normal operieren.
Also wir können zu diesem Inhalt der Adresse von A, was entstehen P
ist, eine 1 addieren und in Q speichern beispielsweise.
Das sieht man am besten anhand von diesem Beispiel hier, also das sind
unsere Adressen.
Also wenn wir Daten speichern im Rechner, dann wissen wir, dass jedes
Datum hat eine bestimmte Adresse und hier sind die Adressen angegeben,
hier ist der Inhalt der entsprechenden Speicherzellen.
Und wenn wir mal unser Code hier angucken, dann sehen wir, das ist ein
Zeiger auf Integer, das ist ein Zeiger auf Integer, A hat den Wert 3,
also der Inhalt von A ist 3, können wir hier sehen.
Also A hat irgendeine Speicheradresse und hat den Wert von 3 und man
sieht hier, durch diese Anweisung P soll eigentlich die Adresse von A
enthalten.
Die Adresse von A ist in hexadezimaler Schreibweise 8004 und man sieht
hier eigentlich, dass das der Inhalt von P ist.
Also der Inhalt von P ist nichts anderes als die Adresse von A und
jetzt was macht die nächste Anweisung hier?
Die nächste Anweisung hier, die addiert zum Inhalt von A eine 1.
Der Inhalt von A ist nichts anderes als eine 3 plus 1 ist 4 und wird
an der Adresse, wo P, wo B eigentlich steht, dort geschrieben.
Das heißt, das ist hier die 4 und hier, genau, das heißt die 4 hier,
der Inhalt von B ist nichts anderes als 3 plus 1.
Und dann haben wir zuletzt diese Tup-Umwandlungsoperation, wo wir
praktisch die Adresse hier an der Stelle nehmen und dann in Q
schreiben.
Also die Adresse hier nehmen und dann als Zeiger auf Integer hier an
der Stelle von Q hier schreiben.
Und damit kann man, wie gesagt, sehr effizient bei strukturierten
Datentypen, also speziell Arrays, operieren.
Nehmen wir mal an, wir hätten so diese Arrays von Integer mit 1, 2, 3
oder Arrays von Characters mit A, B und C oder von Zeichenketten D, E
und dann S mit F.
Und so sieht eigentlich der Inhalt von dem Speicher aus.
Das heißt zunächst mal, für dieses Array N0, 1 und 2, wir nehmen an,
dass jetzt jedes Integer hier 4 Byte lang ist.
Das heißt, das erste Element von dem N-Array ist an der Adresse 8004,
das zweite 8008 und dann 8012.
Und man sieht hier, dass diese Elemente an den einzelnen Stellen
geschrieben werden.
Für C sind das dann die Characters hier, A, B und C, wie es hier
angegeben ist.
Und für S sehen wir, das ist eine Zeichenkette, das wird normalerweise
im Speicher abgelegt, indem man jedes Zeichen speichert und das Ganze
mit einem Null-Byte terminiert.
Das heißt hier diese Terminierung, was man hier sieht und
normalerweise DE, aber wir haben hier nochmal eine Schreibeoperation
auf S1 ausgeführt, indem wir praktisch das E mit F ersetzt haben.
Deshalb steht an der Stelle hier F.
So, das heißt mit Hilfe von diesen Zeigern kann man dann auch auf
diese Inhalte hier sehr einfach und unkompliziert zugreifen, weil man
immer über die Adressen und die Inhalte von bestimmten Speicherzellen
hier arbeiten kann.
Okay, soviel dazu.
Ihr werdet auf dem nächsten Übungsblatt einiges mit dem Thema C und
Zeiger und Operatoren und Kontrollstrukturen zu tun haben.
Und wir sind, wie ich bereits gesagt habe, hier, das heißt wir haben
so diese Ebene angeguckt, das ist die höchste Ebene, die wir in dieser
Vorlesung angucken, die C-Ebene.
Also die höchste Ebene im Sinne von höheren Programmiersprachen und
wir gehen jetzt tiefer eigentlich in der Vorlesung.
Das heißt von C wollen wir mal gucken, wie praktisch solche C
-Programme auf Assembler abgebildet werden und schließlich wie wir von
diesem Assembler-Code auf diesen Maschinen-Code kommen.
Und in der nächsten Vorlesung oder am Ende dieser Vorlesung und am
Anfang von der Vorlesung Digitaltechnik werden wir sehen, wie diese
eigentlich Maschinenbefehle auf Hardwareinstruktionen, tatsächlich auf
Register -Ebene und auf Multiplexer-Ebene und so weiter abgebildet
werden, damit überhaupt eine Operation ausgeführt werden kann.
Es gibt natürlich hier Entwicklungswerkzeuge für C.
Wer ist nicht in der Lage, so einen C-Compiler auf seinem Rechner zu
installieren und damit zu arbeiten?
Jeder ist in der Lage?
Okay, gut.
Das wird auch Thema der Tutorien.
Das heißt die Tutorien werden mit euch alles hier mal durchsprechen.
Derjenige oder diejenige, die Probleme damit hat, soll sich bei uns
melden.
Also entweder bei mir oder bei den Übungsleitern oder bei den Tutoren.
Hier ist nochmal eine Beschreibung, wie man zum Beispiel den GCC
-Compiler für die Leute, die unter Linux arbeiten, installieren kann,
unter Ubuntu, wie man Programme übersetzt und so weiter und so fort.
Aber auch für die Leute, die unter OS X arbeiten und am Ende auch
sogar für die Leute, die unter Windows arbeiten.
Also mit dem Visual Studio, wie man dort auch die entsprechenden
Programme hier erstellen kann, kompilieren kann, debuggen kann und so
weiter und so fort.
Okay, so viel hier zu Kapitel 2, was ich abschließen würde und zum
Kapitel 3 übergehen möchte, wenn es keine Fragen gibt an der Stelle.
Gibt es Fragen?
Okay, das ist nicht der Fall, deshalb machen wir weiter mit Kapitel 3
in der Vorlesung und Kapitel 3 in der Vorlesung beschäftigt sich mit
der Zahlen- und Zeichendarstellung im Rechner.
Wir wollen natürlich Informationen verarbeiten, im Rechner speichern
und verarbeiten.
Deshalb müssen wir wissen und wir wissen, dass Rechner im Dual-System
beziehungsweise jegliche Informationen in die binäre Darstellung oder
digitale Darstellung umwandeln und verarbeiten müssen.
Deshalb müssen wir uns damit beschäftigen, wie werden Zahlen überhaupt
im Rechner dargestellt.
Wie werden nicht nur positive Zahlen, sondern auch negative Zahlen im
Rechner dargestellt.
Also nicht nur die Zeichen von 0 bis 9, sondern auch das Plus und
Minus.
Und wenn wir auch an die Mathematik denken, dann haben wir nicht nur
ganzzählige Zahlen, sondern wir haben auch Zahlen mit Kommas.
Das heißt, die Frage ist, wie wird das Komma im Rechner dargestellt.
Das heißt, die Darstellung von Fest- und Gleitkommazahlen im Rechner.
Und ganz am Ende werden wir auf einige spezielle Codierungen für
Zahlen eingehen und Codierungen für Zeichen, weil wir wollen ja auch
die Buchstaben einer bestimmten Sprache als Sonderzeichen darstellen.
Das ist eine kleine Einführung, die für die meisten wahrscheinlich
trivial ist, dass wir Menschen normalerweise im Dezimalsystem rechnen.
Ich kenne niemanden, der im Dualsystem oder in einem System zu Basis 7
rechnet beispielsweise.
Das wäre auch mal interessant.
Hat jemand eine Idee, warum überhaupt im Dezimalsystem?
Weil der Mensch 10 Finger hat?
Der Mensch hat 20 Finger.
Du hast auch 5 in deinem Fuß, in jedem Fuß.
Also, es gibt viele Thesen dazu, aber natürlich eine von denen ist die
hier.
Und Rechner rechnen im Dualsystem.
Das heißt, eigentlich brauchen wir hier immer eine Konvertierung.
Wir müssen diese Konvertierung nicht unbedingt selber wie Menschen
immer machen, aber wir müssen sie einmal implementieren, damit die
Rechner tatsächlich mithilfe von Oberhauptzahlen oder Informationen
verarbeiten können.
Es gibt weitere Zahlensysteme, wie zum Beispiel das Oktal-Zahlensystem
oder See-Dezimal- oder Hexadezimal-Zahlensystem, wo dann die Basis des
Zahlensystems anders ist.
Also beim Oktal 8 und beim Hexadezimal oder See-Dezimal 16.
Und deshalb müssen wir schauen, was für Zusammenhänge es gibt.
Wie kann man Zahlen im Dezimal beispielsweise umwandeln in Zahlen zu
anderen Basen?
Welche mathematischen Grundlagen braucht man hier?
Und so weiter und so fort.
Und wir fangen damit an.
Wir werden uns nur mit Stellenwertsystemen beschäftigen.
Also Zahlensysteme, wo die Darstellung einer Zahl in Form einer Reihe
von Ziffern und wo jede Ziffer eine Stellenwertigkeit hat.
Das heißt, wenn wir jetzt hier diese Ziffernreihe Zn bis Z-m angucken
und wir nehmen an, dass das eine Ziffernreihe einer Zahl in einem
System zu Basis b ist, dann wissen wir hier, dass zum Beispiel Z0 die
Stellenwertigkeit 0 hat, Z1 die Stellenwertigkeit 1 und so weiter und
so fort.
0, 1 und so weiter und so fort.
Und dass Z-1 die Stellenwertigkeit minus 1 hat und Z-m die
Stellenwertigkeit minus m hat.
So, das heißt eigentlich der Wert von dieser Zahl xp, was durch diese
Ziffernreihe dargestellt oder repräsentiert ist, ergibt sich als eine
Summe von diesen Produkten.
Zn mal b hoch n, Zn minus 1 mal b hoch minus 1, natürlich Z0 mal b
hoch 0, was ja 1 ist und so weiter und so fort.
Das heißt, dafür können wir hier schreiben, das ist die Summe i gleich
minus m bis n, Zi b hoch i.
Und das sind so die interessanten Zahlensysteme in der Informatik.
Also Zahlensysteme, bei denen die Basis entweder 2 ist, das ist das
sogenannte Dualsystem.
Zahlensysteme, bei denen die Basis 8 ist, das Oktalsystem, das
Dezimalsystem und das Seedezimal- oder Hexadezimalsystem mit der Basis
16.
Und natürlich, je nachdem wie groß die Basis ist, hat man einen
bestimmten Satz von Symbolen, mit denen man die Zahlen darstellen
kann.
Und diese Anzahl von Symbolen ist gleich wie die Basis.
Das heißt, bei einem System zu Basis 2 habe ich 2 Symbole, das sind 0
und 1.
Und bei Oktal von 0 bis 7, das heißt, wenn jemand euch eine Zahl gibt
im Oktalsystem und dort taucht die 8 oder die 9 auf, dann ist es keine
gültige Oktalzahl.
Beim Dezimalsystem die Ziffern von 0 bis 9 und beim Seedezimal- oder
Hexadezimal, das sind insgesamt 16 Ziffern, also 0 bis 9 und dann A,
B, C, D, E, F für die Zahlen 10 bis 15.
Was wir hier haben bei uns, beziehungsweise in der Informatik, wir
beschäftigen uns hauptsächlich mit Systemen zu Basis 2.
Das heißt, die Binärziffern 0 und 1, die bezeichnen wir auch als Bits
und das ist so die kleinste Informationseinheit, mit der man sich so
hantiert bzw.
operiert.
Das mag jetzt ein bisschen harmlos oder trivial oder einfach sein,
aber viele von euch denken wahrscheinlich überhaupt nicht darüber
nach, wenn sie irgendeine Zahl im Rechner eingeben und irgendwelche
Operationen ausführen.
Aber wenn es darum geht, arithmetisch-logische Einheiten zu entwickeln
oder Fließkomma-Einheiten auf einem Prozessor, die Fließkomma
-Operationen ausführen, dann haben die Entwickler und die Forscher
dort natürlich eine ganze Reihe von Problemen und von Fragestellungen,
die sie lösen müssen.
Wenn wir mit Papier und Bleistift berechnen, dann haben wir so viel
Papier.
Klar, wir scherzen ein bisschen die Umwelt, aber wir haben genug
Papier, um mit beliebiger Genauigkeit zu berechnen.
Das haben wir bei einem Rechner nicht.
Weil alle Berechnungen bei einem Rechner eigentlich mit Hilfe oder
Unterstützung von Registern erfolgen.
Und diese Register haben eine endliche Breite.
Das heißt, die Genauigkeit, die man normalerweise erzielt, ist
abhängig von der Breite der Register.
Wenn ich ein 16-Bit-Register habe, dann kann ich nicht genauso genaue
Ergebnisse meiner Berechnungen haben, wie wenn ich mit Registern mit
64 -Bit arbeite.
Das heißt, man hat einen endlichen Speicher.
Theoretisch ist es natürlich so, wenn ich unendlich lange Register
habe, dann kann ich natürlich hochgenau rechnen.
Aber wir haben ja einen endlichen Speicher, eine endliche Breite von
Registern.
Das heißt, Zahlen können nur mit einer endlichen Genauigkeit
dargestellt werden.
Und das sind immer so Verbi-Argumente von Chip-Herstellern, die
arithmetisch -logische Einheiten entwickeln.
Mit welcher Genauigkeit kann man rechnen bei dem Prozessor oder bei
dem anderen Prozessor?
Für uns mag es jetzt nicht so interessant sein, im alltäglichen Leben,
mit welcher Genauigkeit man viele Stellen nach dem Komma rechnet.
Aber für viele andere Disziplinen ist es extrem wichtig, also
Statistik, Simulationen und so weiter und so fort.
Finanzwelt.
Es ist extrem wichtig, dass man natürlich mit so vielen wie möglich
Stellen bzw.
mit einer bestimmten Genauigkeit rechnet.
Dann gibt es Überläufe.
Überläufe resultieren daraus, dass wir natürlich auch zur Speicherung
von den Ergebnissen eine endliche Breite von Registern haben im
Rechner.
Das heißt, wir können nicht beliebig genau und beliebig lange
Ergebnisse speichern.
Wir müssen natürlich negative und positive Zellen unterscheiden.
Also mit anderen Worten, wir müssen das Vorzeichen auch im Rechner
darstellen.
Wie macht man das?
Zellen können wir umwandeln und so weiter auf das Vorzeichen.
Da muss man sich bestimmte Konventionen hier überlegen, wie man das
Vorzeichen dort darstellen kann.
Und zuletzt muss man natürlich das Komma darstellen, also speziell
Gleitkomma -Zellen.
Das heißt, wie soll das Komma im Rechner dargestellt werden.
Wir fangen ganz einfach an und zwar, dass wir Zahlen, die zum Beispiel
im Dezimalsystem eingegeben sind, in ein anderes System zu Basis 2.
Für uns im Rechner ist es natürlich interessant, das zu Basis 2 zu
machen, also von Basis 10 zu Basis 2.
Und hier kennt man zwei Algorithmen, mit denen man das machen kann.
Und zwar der erste ist der sogenannte Euclidische Algorithmus, bei dem
die Ziffern sukzessiv beginnen mit der höchstwertigen Ziffer berechnet
werden.
Das heißt, ich habe eine Zahl Z hier, die ist gegeben im
Dezimalsystem, man sieht das hier anhand von dieser Basis, also Zn mal
10 hoch n, Zn minus 1 mal 10 hoch n minus 1 und so weiter und so fort.
Und das ist nichts Neues, was ich hier jetzt, das wäre jetzt okay.
Das heißt eigentlich, was wir hier haben, bei diesen
Stellenwertsystemen hätten wir so etwas, was wir natürlich kennen,
also Beispiel.
Ich nehme mal 1, 9, 8, 4 Komma, das kennen wir ja, 2, 5.
Das ist ein Stellenwertsystem zu Basis 10, das heißt, das ist die
Stellenwertigkeit 0, 1, 2, 3 und das ist minus 1 und das ist minus 2.
Und wir wissen ja eigentlich, dass wir hier zuschreiben können, das
ist 1 mal 10 hoch 3 plus 9 mal
10 hoch 2 plus 8 mal 10 hoch 1 plus 4 mal 10 hoch 0 plus 2 mal 10 hoch
minus 1 plus 5 mal 10 hoch minus 1.
Ja, das sind Stellenwertsysteme, also hier haben wir b gleich 10,
beziehungsweise, das gilt auch im Dualsystem, wenn ich jetzt 1, 0, 1,
0, 1 habe, dann nach wie vor haben wir die Stellenwertigkeit 0, 1, die
Stellenwertigkeit minus 1, minus 2, minus 3 und das wäre also nichts
anderes als 1 mal 2 hoch 1 plus 0 mal 2 hoch 0 plus 1 mal 2 hoch minus
1 plus 0 mal 2 hoch minus 2 plus 1 mal 2 hoch minus 3.
Und wenn wir das hier berechnen, dann sehen wir, dass wir hier zum
Beispiel eine 2 haben, 0,625 zu Basis 10.
So, und was ich jetzt gemacht habe eigentlich, ich habe nichts anderes
als diese Zahl, die im Dualsystem hier angegeben ist, vielleicht
sollen wir das mal hier hinschreiben, das ist hier zu Basis 10 und das
ist hier zu Basis 2.
Ich habe diese Zahl in eine Dezimalzahl umgewandelt, indem ich
sukzessiv hier diese Summen oder Produkte aufaddiert habe.
Jetzt wollen wir aber allgemein sehen, wie diese Konvertierung hier
aussieht.
Das heißt, ich habe eine Zahl in Basis 10 und ich will das in ein
System zu Basis 2 umwandeln.
Und was man hier macht, man sucht zunächst mal nach einer Potenz, die
folgende Gleichung erfüllt.
Wenn meine Zahl z ist und meine Zielbasis b ist, dann suche ich nach
einem p, sodass diese Umgleichung hier erfüllt ist.
Also dass die Zahl hier größer gleich b hoch p und kleiner als b hoch
p minus 1 ist.
Wenn ich das hier habe, dann kann ich natürlich hier die ganzteilige
Division machen und kriege meine Ziffer r, y, i, die erste Ziffer der
Umwandlung.
Und ich kriege einen Rest oder ich habe eventuell einen Rest, den ich
berechnen kann über die Modulooperation von z, i Modulo b hoch i.
Ich wiederhole diesen Schritt so lange, bis natürlich der Rest gleich
0 ist oder der Fehler klein genug ist.
Das Ganze will ich anhand von einem einfachen Beispiel hier zeigen.
Das heißt, nehmen wir mal an, wir wollen die Zahl 13 ins duale System
umwandeln.
Beispiel, also z gleich 13 zu Basis 10 wollen wir umwandeln ins duale
System.
Was machen wir zunächst mal?
Wir müssen nach einer Potenz suchen, die die Gleichung erfüllt.
Dass 13 kleiner gleich als 2 hoch irgendetwas und kleiner als 2 hoch
irgendetwas plus 1.
Und man sieht hier, 2 hoch 3 ist 8, 2 hoch 4 ist 16.
Das heißt eigentlich habe ich hier eine 3 und habe ich hier eine 4.
Was bedeutet das?
Das bedeutet eigentlich, ich fange mal an jetzt diese Zahl 13 durch 2
hoch 3 zu dividieren, also durch 8.
Dann bekomme ich eine 1 und einen Rest von 5.
Im nächsten Schritt, ich wiederhole also diesen Schritt 2, ich nehme
diesen Rest 5 und dividiere das durch 2 hoch 2.
Das ist also 5 durch 4, ist auch eine 1 und ein Rest von 1.
Ich nehme den Rest 1 und ich dividiere durch 2 hoch 1, also 1 durch 2,
ganzzählige Division, ist eine 0 und wiederum ein Rest von 1.
Ich nehme den Rest 1 und dividiere durch 2 hoch 0, also 1 durch 1 ist
gleich 1 und ein Rest von 0.
So, und meine Zahl im Dualsystem lese ich eigentlich ab anhand von
diesen Ergebnissen, die ich hier bekommen habe.
Und zwar die Stellenwertigkeit ist in die Richtung, also wie dieser
Pfeil zeigt.
Das heißt, die Ziffer mit der höchsten Stellenwertigkeit ist die
erste, die ich durch die Division durch 2 hoch 3 bekommen habe.
Was bedeutet das?
Das bedeutet eigentlich, dass Set gleich 13 Dezimal ist gleich, ich
lese, das ist die Stelle mit der höchsten Wertigkeit, dann 1, dann 0,
dann 1, so war es 2.
Und wenn wir das prüfen wollen, dann können wir hier, das ist gleich 1
mal 2 hoch 3 plus 1 mal 2 hoch 2 plus 0 mal 2 hoch 1 plus 1 mal 2 hoch
0, also ist gleich 13.
So, nun kann man das natürlich machen für andere Beispiele.
Nehmen wir an, wir wollen die Dezimalzahl ins Hexadezimalsystem
umwandeln.
Und da gehen wir mal hin und müssen jetzt nach einer Potenz von 16
suchen, zwei Potenzen von 16 suchen, für die die Zahl dann dazwischen
ist.
Das heißt, wir gucken mal hin, 16 hoch 3 und 16 hoch 4 erfüllt die
Gleichung hier, das heißt eigentlich, die höchste Potenz ist 16 hoch
3.
Und dann fangen wir mal an, die Zahl durch 16 hoch 3 dividieren, dann
bekommen wir eine 3, nicht wundern.
Vorher habe ich ins Dualsystem umgewandelt, deshalb waren die
Ergebnisse der Division immer entweder 0 und 1, das sind die Ziffern
von dem Dualsystem.
Und hier bei der Division bekomme ich alles Mögliche, was ich als
Ziffer im Hexadezimalsystem habe, also 0 bis F.
Also wir bekommen eine 3, das ist die erste Ziffer von unserem
Ergebnis, ein Rest von 3453.
Dividieren wir durch 16 hoch 2, dann bekommen wir ein D, 10, 11, 12,
13.
Dann der Rest, 125 durch 6, bekommen wir eine 7 und den Rest von 13.
13 durch 16 hoch 0, also durch 1, ist gleich 13.
13 ist D und der Rest von 0,233.
Wir dividieren durch 16 hoch minus 1, dann bekommen wir eine 3, also
wir multiplizieren eigentlich mit 16.
Dann bekommen wir 3, irgendetwas und dieses Komma irgendetwas ist
unser Rest.
Dann unser Rest hier multipliziert mit 16 hoch 2 oder dividiert durch
16 hoch minus 2, bekommen wir eine 11.
Und dann der Rest hier durch 16 hoch 3, dann bekommen wir eine 10 und
ein Rest von 0,4 mal die 0,8 irgendetwas.
Das können wir entweder beliebig lang machen oder bis dieser
Rundungsfehler klein genug ist.
Das heißt, hier dividieren wir durch 16 hoch 4, dann bekommen wir ein
5 und einen kleinen Fehler von 0,4 mal die 0,129.
Okay, das heißt, diese Zahl hier im Dezimalsystem schreiben wir jetzt
im Hexadezimalsystem wie folgt.
Und wie gesagt, das ist die höchste Stellenwertigkeit, die RC3 hat die
höchste Stellenwertigkeit.
So, ich weiß, dass das Thema eigentlich so langweilig ist, für mich
ist es auch langweilig übrigens.
Ich bin nicht so scharf drauf, das hier zu machen, aber ich mache es
trotzdem, weil ihr glaubt nicht, und ich bin auch kein Freund von
Statistiken, aber ihr glaubt nicht, wie viele Leute bei solchen
einfachen Sachen in der Klausur Punkte verlieren.
Ich finde es so schade, dass man bei solchen Sachen in der Klausur
überhaupt einen halben Punkt verliert.
Das können Schulkinder.
Also mein Sohn in der siebten Klasse kann Dua rechnen.
Das ist ein Scherz.
So,
machen wir weiter.
Wir haben bemerkt eigentlich, dass wir damit beliebige Zahlen, also
ganzzahlige Zahlen, aber auch Zahlen mit Komma von einem System in das
Zielsystem umrechnen können.
Es gibt einen zweiten Algorithmus, mit dem man diese Umwandlung
durchführen kann, und zwar das ist das Horner Schema.
Und der Unterschied ist von dem Horner Schema zu dem Euklidischen
Algorithmus, bevor ich überhaupt das Horner Schema erkläre, dass man
bei dem Horner Schema den ganzzähligen Anteil einer Zahl und den
gebrochenen Anteil einer Zahl separat umwandeln muss, was man bei dem
Euklidischen Algorithmus, wie wir anhand von dem Beispiel vorher
gesehen haben, nicht der Fall ist.
Und wie funktioniert das Ganze?
Wir betrachten das zunächst mal für den ganzzähligen Anteil.
Wir betrachten das zunächst mal für den ganzzähligen Anteil hier.
Und zwar man geht hin, man interpretiert eigentlich diese Zahl hier,
Zn, B hoch N und so weiter, als ein Polynom.
Und man kann das natürlich hier auch anders schreiben, indem man hier
diese Ausklammerung macht.
Das heißt, das Beispiel hier 15741 kann man auf die Art und Weise
machen.
Und bevor ich den Algorithmus vielleicht mal erkläre, weil das ist
wahrscheinlich auch wiederum trivial, will ich das herleiten, wie
dieser Algorithmus funktioniert.
Das heißt, ich interpretiere
diese Darstellung Summe der Produkten, Darstellung einer Zahl als
Summe von Produkten, als ein Polynom.
Ja, das ist ja auch nichts anderes als ein Polynom.
Also Interpretation von XB ist gleich Summe i gleich 0 bis N, ZI, das
sind die Ziffern, B hoch I, als Polynom.
Was bedeutet das?
Das bedeutet eigentlich, wir können das hier hinschreiben, XB ist
gleich Z3, X hoch 3, plus Z2, X hoch 2, plus Z1, X hoch 1, plus Z0, X
hoch 0.
Was habe ich hier gemacht?
Ich habe eigentlich nur B ist gleich X, für die Leute, die so ein
bisschen genau sein wollen.
Und wir wissen, dass das 1 ist, X hoch 0.
Deshalb können wir eigentlich jetzt hier eine Ausklammerung machen.
Und zwar, ich schreibe, das ist Z3, X hoch 2, X plus Z2 mal X plus Z1
mal X plus Z0.
Das kann ich ja schreiben.
Und was mache ich jetzt bei dem Horner Schema?
Also bei dem Euclidischen Algorithmus war der Knackpunkt vorher, dass
wir eine Potenz finden müssen, für die gilt B hoch P, kleiner gleich
die Zahl, kleiner gleich B hoch P plus 1.
Und bei dem hier brauchen wir eigentlich das nicht.
Sondern, wenn ich jetzt diese Zahl hier mal nehme, oder dieses
Polynom, Z3, X plus Z2, multipliziert mit X plus Z1 und das Ganze
nochmal mit X plus Z0, und ich dividiere durch X, ich dividiere durch
die Basis sozusagen.
Was bekomme ich hier?
Ich bekomme natürlich Z3, X plus Z2, multipliziert mit X plus Z1 und
ein Rest von Z0.
Stimmt?
So, wenn ich jetzt den hier nehme, Z3, X plus Z2, X plus Z1, also das
Ergebnis, wenn ich das Ergebnis der Division nehme und nochmal durch X
dividiere, dann bekomme ich natürlich Z3, X plus Z2, multipliziert mit
X und ein Rest von Z1.
Und wenn ich Z3, X plus Z2 nehme, multipliziert mit X,
ich glaube hier brauchen wir keine X, oder?
Okay, wenn wir das hier nehmen und wiederum durch X dividieren, dann
bekommen wir nichts anderes als Z3 und ein Rest von Z2.
Und wenn wir natürlich hier Z3 durch X dividieren, dann bekommen wir
eine Null und ein Rest von Z3.
Was bedeutet das eigentlich?
Das bedeutet, das was ich hier bekommen habe, diese Z hier, und zwar
in der Reihenfolge von Z0 bis Z3 sind nichts anderes als meine Zahlen.
Das sind eigentlich die Ziffern, die meine Zahl beschreiben.
Also XB ist gleich Z3, Z2, Z1, Z0.
Was bedeutet das für uns?
Das bedeutet eigentlich, ich brauche keine Potenz zu suchen zunächst
einmal.
Ich nehme die Zahl und ich dividiere die Zahl sukzessive, das kommt
hier, ich dividiere die Zahl sukzessive durch B, dann bekomme ich
irgendein Ergebnis plus ein Rest.
Und diese Reste, die ich hier bekomme, sind nichts anderes als mein
Ergebnis.
Nehmen wir mal die gleiche Zahl hier, aber das ist nur der ganzteilige
Anteil der Zahl.
Und wir wollen das auch in Hexadezimal umwandeln, genau wie vorher.
Dann dividieren wir durch 16, weil die Basis ist 16, bekommen wir
irgendetwas und den Rest von 13, das ist D.
Dann das Ergebnis durch 16, bekommen wir 61 und den Rest von 7.
61 durch 16 ist 3 und den Rest von 13.
3 durch 16 ist 0 und den Rest von 3.
Und somit haben wir eigentlich 3D7D.
3D7D ist nichts anderes als die Zahl 15741 in Hexadezimal.
Das gleiche müssen wir natürlich machen für den Nachkommateil, also
für den gebrochenen Teil.
Also von I gleich minus M bis 0.
Und das erfolgt eigentlich, kann man entsprechend auch schreiben, wie
man sieht hier.
Also sukzessive Multiplikation des Nachkommateils der Dezimalzahl mit
der Basis B des Zielsystems.
Dann bekommen wir die YIs in der Reihenfolge von der höchstwertigen
bis zur niedrigwertigen Nachkommaziffer.
Das heißt, wenn wir 0,23310 ins Hexadezimal umwandeln, multiplizieren
wir mal mit 16.
Dann bekommen wir 3, irgendetwas und 3 wäre jetzt Z-1.
Dann dieses irgendetwas nochmal mit 16 bekommen wir 11, also ist B im
Hexadezimal, Komma irgendetwas, irgendetwas mit 16, das ist 10, also A
im Hexadezimal, Komma irgendetwas und so weiter und so fort.
Das heißt, 0,233 zu Basis 10 ist gleich 0,3BA5 zu Basis 16.
Und natürlich als Abbruchkriterium, immer wenn man hier eine 0
bekommen hat oder wenn man eine gewünschte Genauigkeit erreicht hat.
Der Wert von der Zahl ergibt sich dann als Summe der einzelnen Stellen
und zwar von i gleich minus m bis i gleich n.
Können wir das hier nochmal gucken, wie das aussieht.
Zum Beispiel die Umwandlung jetzt von Dual in Dezimal.
Wiederum, das ist ein Stellenwertsystem.
Das heißt, wir wissen, das ist 2 hoch minus 4, das ist 2 hoch minus 3,
2 hoch minus 2, 1 und das ist 2 hoch 0, 1, 2, 3, 4.
Und wir können das hier summieren, dann kommen wir auf die Zahlen, die
wir hier gesehen haben.
So, vielleicht noch ein weiteres Beispiel zum Horner Schema oder wollt
ihr nicht?
Ne?
Ok.
Dann könnt ihr das zu Hause machen.
Also Hausaufgabe oder Beispiel.
Könnt ihr in der Klausur auftauchen.
Also z gleich 28,25 zu Basis 10 in eine Zahl
zur Basis 7.
Das wollt ihr nicht, deshalb lassen wir das.
Das könnt ihr als Hausaufgabe mal schauen.
Das Ergebnis hat nur 4 Ziffern eigentlich.
Also denkt mal darüber nach.
Also das Ergebnis besteht nur aus 4 Ziffern.
Ok.
Gut.
Das nächste.
Umwandlung jetzt beliebiger Stellenwertsysteme.
Das heißt, wir können jetzt von Dual zu Hexadezimal, Dezimal und so
weiter und so fort.
Aber im Prinzip können wir eigentlich jede Zahl in ein beliebiges
Zielsystem umwandeln.
Die einfachste und blödeste vielleicht Variante, die Zahl in das
Dezimalsystem umzuwandeln und alles, was wir bis jetzt gelernt haben,
anzuwenden, um vom Dezimalsystem auf den Rest zu kommen.
Es gibt einige Spezialfälle, die für uns sehr interessant sind in der
Vorlesung, in der Informatik allgemein.
Und zwar, wenn die Basis, die wir behandeln oder die Basis von dem
Zielsystem eigentlich, wo wir umwandeln wollen, eine Potenz der
anderen Basis ist, dann können einfach mehrere Stellen zu einer Ziffer
zusammengefasst werden oder eine Stelle kann durch eine Folge von
Ziffern ersetzt werden.
Was bedeutet das?
Wenn wir eine Basis haben, die eine Potenz der anderen Basis ist, bei
allen Zahlensystemen, die wir heute erwähnt haben, bis auf Dezimal.
Also Dual, Oktal, Hexadezimal, weil Oktal ist die Basis 8, ist 2 hoch
3.
Hexadezimal ist die Basis 16, also 2 hoch 4.
Und das sieht man hier.
Das heißt, wenn ich eine Zahl vom Dualsystem ins Hexadezimalsystem
betrachte, dann weiß ich, dass 16 ist 2 hoch 4.
Kann ich also vier Dualstellen zu einer Hexadezimalstelle
zusammenfassen.
Worauf man hier nur achten muss bei dieser Zusammenfassung, man muss
immer beim Komma anfangen.
Beim Komma nach links, dann hat man hier diese Vorkommastellen und
beim Komma nach rechts, dann bekommt man die Nachkommastellen.
Das heißt, 1 0 1 0 ist die 4 und 1 1, man sieht hier, ich habe keine
weitere 0, aber ich kann es ergänzen mit 0, das ändert nichts
eigentlich an dem Wert der Zahl.
Dann ist es die 3.
Hier habe ich 8 plus 5, das sind 13, das ist also D und 0 1
interpretiert jetzt als eine Dezimalzahl und ergänzt mit diesen Nullen
hier ist nichts anderes als 4.
Das heißt eigentlich für diese Dualzahl kann ich die direkt in
Hexadezimal umwandeln.
Ich kann sie auch direkt in Oktal umwandeln.
Also Oktal, das heißt ich habe die Zahl hier 0 0 1 0 1 1 0, 1 1 0 1 0
1 und was machen wir bei Oktal?
Wir machen das gleiche, aber wir wissen bei Oktal, dass wir hier 2
hoch 3 ist gleich 8, also wir fassen Dreiergruppen.
Wir bilden Dreiergruppen und zwar immer vom Komma ausgehend und
natürlich ergänzen wir hier die 0, das ist wiederum auch eine
Dreiergruppe.
Und dann kann ich hier schreiben, das ist natürlich eine 4, das ist
hier eine 6 und das ist eine 0, das ist hier eine 6 und das ist hier
eine 5 zu Basis 8.
So, das heißt auf die gleiche Art und Weise können wir natürlich diese
Zusammenfassung oder Gruppierung von Ziffern in Dreiergruppen bzw.
Vierergruppen im Oktalen oder Hexadezimalen Fall, können wir die
Umwandlung hier auf einfache Art und Weise erledigen.
Gut, somit können wir eigentlich alle möglichen Zahlen zwischen
beliebigen Zahlensystemen umwandeln.
Die nächste Frage oder die nächste Herausforderung für uns ist, wie
kann man das Vorzeichen, also Plus und Minus eigentlich im Rechner
darstellen.
Mit anderen Worten, wie kann man negative Zahlen im Rechner
darstellen.
Und hierzu gibt es vier Möglichkeiten, die wir behandeln wollen.
Wer kennt das eigentlich?
Also mich würde interessieren, wer kennt den Stoff aus der Schule, aus
anderen Vorlesungen?
Okay, die Hände gehen zu halb, also einer winkt hoch.
Ah, okay, hallo Karl.
Und wer kennt das nicht?
Ali, Eini.
Eini kennt das nicht und wer noch?
Okay, gut, jetzt nehmen wir jemanden von allen, die gesagt haben, sie
kennen den Stoff und der rechnet so eine Aufgabe für uns hier.
Also, wer hat die Hand hoch?
Komm raus.
So, wir nehmen jetzt jemanden.
Ja, ja, ja, da vorne, hier.
Lass ihn raus, du kannst mal springen.
So, also,
das ist natürlich sehr mutig, ja, also vielen Dank.
Du darfst die Zahl wählen und ich bestimme, in welche Darstellung sie
umgewandelt werden soll.
Moment, wir geben dir ein Mikro.
Also, nehmen wir mal was Einfaches, weißt du, nehmen wir mal 17, zur
Basis 10.
Ja, soll ich dir das Mikro geben?
Du bist nervöser als ich.
Und ich kann so reden.
Und jetzt wollen wir von dir, wie heißt du nochmal?
Ich bin der Karl.
Der Karl, habe ich mir gedacht, ja.
So, jetzt wollen wir diese Zahl in zweier Kompliment haben.
Zweier Kompliment, ok.
Ich kann dir verraten, wie die Zahl in Dual aussieht, ja.
Also, die Zahl in Dual ist natürlich 17 plus 1, 16 plus 1, 1, 0, 0, 0,
also 4, 3, 2, 1, 0.
So, das ist die Zahl in Dual.
Das ist nicht die Aufgabe.
Die Aufgabe, wir wollen diese Zahl in zweier Kompliment haben.
Wow.
Das ist sehr gut, ja.
Das ist in der Tat sehr gut, ja.
Aber was ist der Unterschied eigentlich?
Warum hat er diese 0 da hingeknallt?
Wer weiß es nicht?
Wir fragen, wer weiß es nicht?
Ok, sie weiß es nicht.
Jetzt musst du erklären.
Ich muss es erklären, ok.
Zweier Kompliment Darstellung ist eine Art der Darstellung, damit man
auch negative Zahlen erkennen kann.
Und das ist so, dass wenn eine, also der Grund, wieso das jetzt die 17
ist und nicht die minus 17, ist, weil eben vorne eine 0 steht und
nicht eine 1, wenn man es einfach sagt.
Also wenn ich das nicht wusste, dann hätte ich das anhand deiner
Erklärung nicht verstanden.
Ich bin sehr schlecht im Erklären.
Ok, vielleicht kannst du hier das Zweier Kompliment von minus 17.
So.
Also eigentlich, das ist der Fehler, den alle machen, wenn man fragt,
was ist das Zweier Kompliment oder wie sie die Darstellung von 17 in
Zweier Kompliment formen.
Und die Darstellung von 17 in Zweier Kompliment Form ist richtig, wie
Karl das gemacht hat.
Das ist, was die wenigsten eigentlich richtig machen.
Weil alle fangen damit an, wenn sie das Wort Zweier Kompliment hören,
das zu machen, was er hoffentlich jetzt machen wird.
Und denken nicht darüber nach, das ist eine positive Zahl, die 17.
Ich brauche nichts dazu machen.
Ich muss nur gucken, dass ich den Zahlenbereich oder den
Darstellungsbereich so groß wähle, dass tatsächlich meine Zahl als
Zweier Kompliment Zahl dort steht.
Und Zweier Kompliment Zahl bedeutet, dass natürlich das höchstwertige
Bit bei einer positiven Zahl positiv ist und deshalb hat er die Null
hinzugefügt.
Und jetzt will er uns erklären, wie er das Zweier Kompliment von minus
17 berechnet.
Oder minus 17 in Zweier Kompliment Darstellung, wie das aussieht.
Im Einser Kompliment war es sowas.
Das ist jetzt nicht das Zweier Kompliment, sondern das Einser
Kompliment.
Das Problem im Einser Kompliment ist, du hast zwei Null Darstellungen.
Das heißt, beim Zweier Kompliment hat man dieses Problem nicht mehr.
Das heißt, jetzt ist es anders.
Und zwar war es jetzt, glaube ich...
Ja, wo fängst du an?
Wie kommst du jetzt auf diese 1, 0, 1, 1, 0?
Das ist das Einser Kompliment.
Da dreht man einfach die Nullen in eine 1 und die 1 in eine 0.
Ja, aber von wo fängst du an?
Fängst du von der ersten Zahl, die ich geschrieben habe, oder von
deiner?
Von meinem Kopf aus.
Nein, wir brauchen nicht von deinem Kopf.
Also du musst den Rechenweg angeben.
Scheiße.
Tja, also...
Eins...
Du hast noch eine Minute, ja?
Null...
Und jetzt war es...
Ich bin mir nicht sicher.
Aber ich glaube...
Ja!
Du glaubst nicht, wie sehr ich mich freue.
Das ist Feindarbeit.
Okay, also...
Das ist eigentlich...
Ich muss dir auch helfen.
Das ist eigentlich...
Danke, danke.
Das ist eigentlich der Grund...
Der ist nicht mein Sohn, übrigens.
Das ist nicht mein Sohn, den ich gesagt habe, er kann Dua rechnen.
So, das ist eigentlich der Grund, warum wir das durchkauen mit euch.
Weil das sollte echt rückzugehen.
Also für mich ist es natürlich ganz einfach, ja?
Also ich nehme seine Zahl hier und ich wandle sie in...
Ich kippe sie, also konvertiere sie bitweise und addiere eins dazu,
bekomme ich eins, eins, eins, eins.
Null, eins.
Natürlich ist es eine negative Zahl, weil das höchstwertige Bit ist
eins und den Wert können wir auch bestimmen oder prüfen.
Also deshalb werden wir am Dienstag weiter mit dem Thema machen, ja?
Damit das für jeden von euch wie im Schlaf, also das muss tatsächlich
aus dem Rückenmark kommen.
Aus dem Rückenmark, ohne dass irgendwelche Operationen im Gehirn
erfolgen sollen, wenn es darum geht, Zahlen umzuwandeln.
Für heute bedanke ich mich für die Aufmerksamkeit und wünsche euch
noch einen schönen Tag, ja?
Tschüss.