{"id":9223,"date":"2023-07-06T07:00:00","date_gmt":"2023-07-06T05:00:00","guid":{"rendered":"https:\/\/gilbertbrands.de\/blog\/?p=9223"},"modified":"2023-07-05T17:01:30","modified_gmt":"2023-07-05T15:01:30","slug":"warum-berechnungen-falsch-sein-koennen-2","status":"publish","type":"post","link":"https:\/\/gilbertbrands.de\/blog\/2023\/07\/06\/warum-berechnungen-falsch-sein-koennen-2\/","title":{"rendered":"Warum Berechnungen falsch sein k\u00f6nnen (2)"},"content":{"rendered":"\n<p>In der ersten Folge haben wir uns ein Ursachengebiet f\u00fcr unsinnige Rechenergebnisse vorgenommen, das man &#8222;Numerische Mathematik&#8220; nennt. Da ich selbst mal in der Lehre t\u00e4tig war, wei\u00df ich, dass man das nur wenigen Programmierern wirklich nahe bringen kann. Ob genau diese dann auf den Arbeitsstellen landen, wo man das ber\u00fccksichtigen muss? Wer wei\u00df?<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>Heute im Teil 2 geht es um Programmierung. Jeder wei\u00df vermutlich um die Vielzahl der Programmiersprachen. Warum es nicht nur eine gibt, d\u00fcrfte klar sein: f\u00fcr die Umsetzung einer bestimmten Aufgabe nimmt man eben das geeignetste Werkzeug. Um eine Dachlatte zu k\u00fcrzen gen\u00fcgt ein einfacher Fuchsschwanz; eine lasergesteuerte Pr\u00e4zisionss\u00e4ge tut es zwar auch, aber bis man die programmiert hat, ist das Dach bereits fertig.<\/p>\n\n\n\n<p>Einfache Programmiersprachen \u00fcbersetzen w\u00e4hrend der Laufzeit, also wenn das Programm abgearbeitet wird, jede Programmzeile in Maschinencode und wertet diesen aus. F\u00fcr viele Zwecke gen\u00fcgt das und man kann schnell und einfach \u00fcberschaubare Anwendungen programmieren. <\/p>\n\n\n\n<p>Solche Programme sind aber nur begrenzt schnell. Die n\u00e4chste Stufe besteht folglich darin, erst das komplette Programm zu \u00fcbersetzen (zu compilieren) und dann abarbeiten zu lassen. Solche Programme sind au\u00dferordentlich schnell, zumal der Programmierer, wenn er nicht ganz doof ist, sich auch \u00fcberlegen kann, wie er die Maschine durch seinen Code unterst\u00fctzen kann. Allerdings ist zu sagen: je komplexer die Programme werden, desto schwieriger wird es, die \u00dcbersicht zu behalten, weil der Programmierer bei \u00c4nderungen immer das ganze Programm oder doch sehr gro\u00dfe Anteile im Blick behalten muss.<\/p>\n\n\n\n<p>Wenn es noch schneller werden muss, m\u00fcssen die Programme &#8222;parallelisiert&#8220; werden, d.h. Teile, die nicht voneinander abh\u00e4ngen, werden auf verschiedenen Maschinen ausgef\u00fchrt. Das ist ein Kapitel f\u00fcr sich und wer so etwas mal selbst ausprobieren m\u00f6chte, sei auf das Buch &#8222;Parallele Programmierung&#8220; verwiesen (rechts im Slider).<\/p>\n\n\n\n<p>Das Problem sind gro\u00dfe Programm, bei denen der Programmierer die \u00dcbersicht verlieren kann. Ein erster Schritt besteht darin, Daten, die zusammen geh\u00f6ren, auch zusammen zu verwalten:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/Anstatt von Feldern\n\nstring[2] name;\ndouble[2] value;\n\n\/\/ Strukturen\n\nstruct Haus {\n    string name;\n    double value;\n};\n\nstruct Auto {\n    string name;\n    double value;\n};<\/code><\/pre>\n\n\n\n<p>Der Programmierer kann also nicht mehr durch einen falschen Index im Feld daneben greifen. Es k\u00f6nnen allerdings immer noch falsche Zugriffe erfolgen, beispielsweise indem die Werte an Stellen ge\u00e4ndert werden, an denen das nicht passieren soll. Man ist dann schnell darauf gekommen, dass Compilersprachen auch geeignet sind, den Programmierer daran zu hindern, solche Fehler zu machen, in dem Datenstruktur und Code miteinander verkn\u00fcpft werden:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class Haus {\npublic:\n    string const&amp; get_name() const;\n    void set_name(string const&amp;);\nprivate:\n    string name;\n    double hoehe;\n};\n...\nvoid function(Haus const&amp; h){\n    string s = h.get_name();\n    string t = \"hallo haus\";\n    h.set_name(t);  \/\/ Compilerfehler\n    h.name = t;     \/\/ Compilerfehler\n}<\/code><\/pre>\n\n\n\n<p>Durch die Kennzeichnung &#8222;private&#8220; kann kein anderer Programmteil mehr auf den Namen direkt zugreifen. Der Compilert wirf beim Zugriff &#8222;h.name&#8220; einen Fehler aus und \u00fcbersetzt nicht weiter. Die Kontrolle geht sogar noch weiter: die Variable &#8222;h&#8220; ist im Funktionsaufruf als &#8222;const&#8220; deklariert, d.h. sie wird in der Funktion nicht ge\u00e4ndert. Das &#8222;const&#8220; bei &#8222;get_name&#8220; garantiert das, &#8222;set_name&#8220; besitzt aber kein &#8222;const&#8220; und der Compiler h\u00f6rt mit einem \u00dcbersetzungsfehler auf. Auch in l\u00e4ngeren Codes kann der Programmierer keine Fl\u00fcchtigkeitsfehler dieser Art mehr machen.<\/p>\n\n\n\n<p>&#8222;Funktionsaufruf? Wird das dadurch nicht langsamer?&#8220; &#8211; h\u00e4ngt von der Sprache ab. Das hier ist C++ &#8211; Code, und der Compiler wirft den Funktionsaufruf bei der Optimierung raus. Es wird also nicht langsamer, nur der Code wird l\u00e4nger, daf\u00fcr aber deutlich sicherer. Wir sind jetzt \u00fcbrigens schon bei der objektorientierten Programmierung.<\/p>\n\n\n\n<p>Da viel Code direkt in die Klassen verschoben werden kann, weil ohnehin nur die eigenen Daten manipuliert werden, wird der allgemeine Code f\u00fcr die Algorithmen nat\u00fcrlich k\u00fcrzer und \u00fcbersichtlicher und die Fehlersuche einfacher: funktioniert es mit einer Klasse nicht, liegt der Fehler da, funktioniert es gar nicht, beim allgemeinen Code.<\/p>\n\n\n\n<p>Schauen wir zwei weitere Probleme anhand folgenden Beispiels an:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class Mobil{\npublic:\n    virtual string get_name() const {return \"Mobil\";}\nprotected:\n    string name;\n};\n\nclass Auto: public Mobil {\npublic:\n    string get_name() const {return \"Auto\";}\n};\n\nclass LKW: public Mobil {\npublic:\n    string get_name() const {return \"LKW\";}\n};\n\nvoid func(Mobil const&amp;  m1, Mobil const&amp; m2){\n    string s = m1.get_name() + \" \" + m2.get_name();\n    cout &lt;&lt; s &lt;&lt; endl;\n}\n....\n    Auto m1;\n    LKW m2;\n    func(m1,m2);\n\nOutput: Auto LKW<\/code><\/pre>\n\n\n\n<p>Die beiden Objektklassen &#8222;Auto&#8220; und &#8222;LKW&#8220; sollen im gleichen Umfeld genutzt werden, aber nicht mit &#8222;Haus&#8220; in Konflikt kommen. Der Trick hei\u00dft hier Vererbung: &#8222;Auto&#8220; und &#8222;LKW&#8220; haben die gemeinsame Eigenschaft &#8222;Mobil&#8220;. Die Methode &#8222;func&#8220; akzeptiert jede Variable, die von &#8222;Mobil&#8220; abgeleitet ist. Ein Objekt des Typs &#8222;Haus&#8220; f\u00fchrt wieder zum Streik des Compilers, weil zwar die Methodennamen passen w\u00fcrden, &#8222;Haus&#8220; aber nicht von &#8222;Mobil&#8220; abgeleitet ist. Und wie man sieht, werden innerhalb der Funktion die Methoden von &#8222;Auto&#8220; und &#8222;LKW&#8220; ausgef\u00fchrt, obwohl in der \u00dcbergabeschnittstelle nur die Elternklasse &#8222;Mobil&#8220; definiert ist. Dem Programmierer werden so weitere M\u00f6glichkeiten f\u00fcr Fl\u00fcchtigkeitsfehler verbaut und auch die Fehlersuche wird einfacher, da der Code noch weiter objektspezifisch wird.<\/p>\n\n\n\n<p>Damit w\u00e4re zwar Ordnung in die Daten gebracht, aber noch nicht unbedingt in die Algorithmen. Man kennt oft die Algorithmen vom Prinzip her sehr gut, wei\u00df aber noch nicht, auf welche Datentypen sie letztlich angewandt werden m\u00fcssen. Es bietet sich an, sie daten- und strukurtypunabh\u00e4ngig zu programmieren und den Datentyp erst sp\u00e4ter beim Compilieren nachzureichen. Damit w\u00e4ren wir bei der generischen oder Meta-Programmierung. Beispielsweise kann es zu Beginn der Programmierung noch nicht gekl\u00e4rt sein, wie man viele Objekte verwaltet. In Feldern? In Listen? Die Standardbibliotheken bieten unterschiedliche M\u00f6glichkeiten an, die man mit dem passenden Typ implementiert:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    vector&lt;int> v;   \/\/ oder wahlweise\n\/\/  list&lt;int> v;<\/code><\/pre>\n\n\n\n<p>Den Algorithmus kann man unabh\u00e4ngig von der Wahl implementieren<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    vector&lt;int>::iterator it;  \/\/ oder wahlweise\n\/\/  list&lt;int>::iterator it;\n\n    for(it=v.begin();it!=v.end();it++)\n        func(*it);<\/code><\/pre>\n\n\n\n<p>Das Feld wird mit dem Datentyp &#8222;int&#8220; implementiert, mit dem internen Datentyp &#8222;iterator&#8220; kann man durch das Feld wandern und der &#8222;iterator&#8220; wei\u00df auch gleich, welcher Datentyp beim Zugriff erzeugt (n\u00e4mlich &#8222;int&#8220;)  und an die Funktion \u00fcbergeben wird. Gef\u00e4llt einem der &#8222;vector&#8220; aus irgendeinem Grund nicht, kann man ihn durch &#8222;list&#8220; ersetzen, muss aber sonst im Code nichts ver\u00e4ndern.<\/p>\n\n\n\n<p>Die Algorithmen werden mit einer Mustervariablen, einem Template definiert:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>template &lt;class T> class vector{\npublic:\n    typedef T* iterator;\n    ...\nprivate:\n    T* data;    \n    ...\n};<\/code><\/pre>\n\n\n\n<p>Sieht einfach aus, ist es aber nicht. Beispielsweise besitzt der Iterator die Grunddefinition<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>template&lt;\n    class Category,\n    class T,\n    class Distance = std::ptrdiff_t,\n    class Pointer = T*,\n    class Reference = T&amp;\n> struct iterator;<\/code><\/pre>\n\n\n\n<p>um tats\u00e4chlich alle Speichertypen einfach austauschbar zu machen und auch mit allen Objekttypen zu finktionieren. Metaprogrammierung erlaubt auch die Erzeugung von Datentypen nach Ma\u00df mit Hilfe von Objekt-Fabriken, die abenteuerliche Schnittstellen wie<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>template &lt;\n    class TList,\n    template&lt;class> class Unit = AbstractFactoryUnit\n> class AbstractFaktory ;<\/code><\/pre>\n\n\n\n<p>besitzen, wobei &#8222;TList&#8220; und &#8222;AbstractFactoryUnit&#8220; ihrerseits komplexe Definitionen besitzen. Generische Compiler sind in der Lage, Algorithmen auf Datenstrukturen zur Compilezeit auszuf\u00fchren,  die in ihrer Komplexit\u00e4t kaum hinter den Algorithmen auf den Daten zur Laufzeit zur\u00fcck stehen. \u00b9\u207e Stimmt etwas nicht, bricht der Compiler ab und unn\u00fctze Rechenzeit f\u00fcr die Daten wird gar nicht erst verschwendet.<\/p>\n\n\n\n<p>Letztlich kann man mit solchen Techniken daf\u00fcr sorgen, dass dem Programmierer kaum noch grunds\u00e4tzliche Fehler unterlaufen k\u00f6nnen. Allerdings erfordern diese Techniken ein entsprechendes Abstraktionsverm\u00f6gen und es dauert eine ganze Weile, bis die Grundlagen gelegt sind (und auch oft einige Zeit, bis man als hinzukommender Nutzer oder Teilnehmer die Gesamtkonstruktion durchschaut hat). In dieser Zeit haben die Programmierer, die irgendwo bei &#8222;struct&#8220; oder davor ansetzen, vermutlich schon viele Berechnungen geliefert, aber m\u00f6glicherweise auch Schrott, von dem man nicht wei\u00df, wo die kaputten Ergebnisse herkommen.<\/p>\n\n\n\n<p>Die Fragen, die man stellen muss, lautet somit: welche Programmiersprache wurde eingesetzt? Ist sie typsicher genug, um Programmiererfehler auszuschlie\u00dfen bzw. schnell zu erkennen? Ist die Programmstruktur kontrollierbar oder aufgrund der Komplexit\u00e4t nicht kontrollierbar? \u00b2\u207e<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p class=\"has-small-font-size\">\u00b9\u207e &#8222;Templates? Kein Problem! K\u00f6nnen wir!&#8220; Das behauptet man aber nur so lange, wie man es mit Leuten zu tun hat, die generische Programmierung auch nicht verstanden haben. Und das sind relativ viele. Die gute Firma MicroSoft musste \u00fcber viele Jahre hinweg immer wieder zugeben, dass sie im Vergleich mit den GNU-C++ Compilern leider nur C++ auf der Packung stehen haben, aber keines drin ist (ist nat\u00fcrlich l\u00e4ngst erledigt; vermutlich, seit Bill &#8222;the Gates&#8220; die Compilerbauer nicht mehr behindert, weil er jetzt in Pharma, Klima und Bodenspekulation macht). In Algorithmen Zahlen zu verwursten oder bestimmte Datenobjekte zu finden ist f\u00fcr viele schon schwer genug; das und anderes auch mit Datentypen zu machen ist noch ein paar Etagen h\u00f6her aufgehangen, weil eben komplett abstrakt. Das ist fast so \u00e4hnlich als h\u00e4tte man begriffen, was es mit Polynomen und deren Nullstellen auf sich hat und dann auf <a rel=\"noreferrer noopener\" aria-label=\"->das hier (\u00f6ffnet in neuem Tab)&#8220; href=&#8220;https:\/\/de.wikipedia.org\/wiki\/Galoistheorie&#8220; target=&#8220;_blank&#8220;>-&gt;das hier<\/a> st\u00f6\u00dft.<\/p>\n\n\n\n<p class=\"has-small-font-size\">\u00b2\u207e Nicht erw\u00e4hnt haben wir den Begriff &#8222;Exception&#8220;, also die Behandlung von Ausnahmesituationen. Das kann in sehr komplexen Programmen ebenfalls von Bedeutung sein. In kaum \u00fcberschaubaren Programmen lassen sich Fehler nicht korrekt lokalisieren und Korrekturen werden m\u00f6glicherweise an Stellen angebracht, wo das Kind schon im Brunnen liegt. Der Grund ist, dass man den normalen Programmfluss nicht verlassen kann, d.h. wenn man in die 5. Unterfunktion eingestiegen ist, muss man auch \u00fcber alle 5 wieder zur\u00fcck. Die Exception bietet die M\u00f6glichkeit, \u00fcber s\u00e4mtliche Funktionen zur\u00fcck von der Stelle, wo die &#8222;Ausnahme&#8220; (ich vermeide bewusst Fehler) aufgetreten ist, zur\u00fcck in eine Programmebene zu springen, von der aus die Berechnung mit auf die Ausnahme angepasstem Code fortgef\u00fchrt werden kann. <\/p>\n","protected":false},"excerpt":{"rendered":"<p>In der ersten Folge haben wir uns ein Ursachengebiet f\u00fcr unsinnige Rechenergebnisse vorgenommen, das man &#8222;Numerische Mathematik&#8220; nennt. Da ich selbst mal in der Lehre t\u00e4tig war, wei\u00df ich, dass man das nur wenigen Programmierern wirklich nahe bringen kann. Ob genau diese dann auf den Arbeitsstellen landen, wo man das ber\u00fccksichtigen muss? Wer wei\u00df? Download &hellip; <a href=\"https:\/\/gilbertbrands.de\/blog\/2023\/07\/06\/warum-berechnungen-falsch-sein-koennen-2\/\" class=\"more-link\"><span class=\"screen-reader-text\">Warum Berechnungen falsch sein k\u00f6nnen (2)<\/span> weiterlesen <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-9223","post","type-post","status-publish","format-standard","hentry","category-allgemein"],"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/gilbertbrands.de\/blog\/wp-json\/wp\/v2\/posts\/9223","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/gilbertbrands.de\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/gilbertbrands.de\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/gilbertbrands.de\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/gilbertbrands.de\/blog\/wp-json\/wp\/v2\/comments?post=9223"}],"version-history":[{"count":3,"href":"https:\/\/gilbertbrands.de\/blog\/wp-json\/wp\/v2\/posts\/9223\/revisions"}],"predecessor-version":[{"id":9227,"href":"https:\/\/gilbertbrands.de\/blog\/wp-json\/wp\/v2\/posts\/9223\/revisions\/9227"}],"wp:attachment":[{"href":"https:\/\/gilbertbrands.de\/blog\/wp-json\/wp\/v2\/media?parent=9223"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/gilbertbrands.de\/blog\/wp-json\/wp\/v2\/categories?post=9223"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/gilbertbrands.de\/blog\/wp-json\/wp\/v2\/tags?post=9223"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}