 Wunderbar, schön, dass ihr alle da seid. Willkommen zu dem Talk, ein WTF für mehr FPS. Heute lernen wir etwas über ein Algorithmus, der ziemlich kult ist. Auch schon ein paar Jährchen auf dem Buckel hat, aber ein paar sehr schöne Grundlagenprinzipien, die Informatik implementiert. Und dafür bitte ich Sebastian auf die Bühne, unseren Speaker. Sebastian, schön, dass du da bist. Und wir freuen uns sehr, dass du uns gleich mal erzählst, was das nicht in die Vorlesung geschafft hat. Ja, vielen, vielen Dank für die Einführung. Und ja, ich bin überrascht, wie viele hier sind. Ich habe das eigentlich auch nur eingereicht, weil ich mir dachte, das könnte vielleicht auch jemand interessieren und wahrscheinlich kriege ich das nicht irgendwo anders noch mal wieder unter. Und ja, jetzt stehe ich hier im großen Saal und es kommt immer noch Leute rein. Ich hoffe, ich kann ein wenig die Mysterien lüften, die hinter dem Algorithmus stehen, weil er einfach ein paar Sachen darstellt, wo ich denke, es sollte am klar werden, dass ein paar ganz grundlegende Dinge aus dem Studium, die vielleicht einige schon mal gesehen haben in der Schule, tatsächlich da drin vorkommen, auch wenn das nicht so aussieht. Genau. Das heißt, worum Gewitz wird es hier in dem Vortrag gehen? Ich werde ein bisschen über die Analyse des Algorithmus sprechen. Es haben schon viele, viele vorher getan. Es gibt auch ganz viele Publikationen dazu, ganz viele Vorträge. Aber ich möchte einfach nochmal einen kleinen Aspekt noch weiter dazu beitragen und vielleicht dem ein oder anderen hier auch nochmal das Ganze ein bisschen näher bringen. Das heißt, es geht hier ein bisschen um die Grundlagen der IT und der Mathematik, die da drin vorkommen. Dann gucken wir uns mal den Algorithmus an, wie funktioniert er eigentlich? Ja, und was können wir heute daraus lernen und warum ist es heute auch noch so wichtig, wie dieser Algorithmus funktioniert? Ja, nur als Vorwarnung ein bisschen vorhin habe ich aus den Vorlesungen übernommen. Also, worum geht es hier? Wir haben es ja schon gehört, der hat schon eine gewisse Geschichte auf dem Buckel. Na, es geht um Quake, Quake Arena. Und die Leute, die es gespielt haben, haben festgestellt, das kann schön süchtig machen. Und ja, auch in den ganzen Reviews war das Spiel berühmt dafür fürs Gameplay, fürs wirklich dieses Multiplayer Gameplay, das First Person Shooters, aber auch für die abartig große Geschwindigkeit. Wie viele Frames per Second da tatsächlich rüber kommen, das war schon damals einfach überraschend. Und ja, das ist eben die Frage, woran liegt es, dass dieses Spiel so schnell geworden ist? Und ein Punkt ist, als das danach veröffentlicht wurde, wie so oft von ID Software, ist Leuten sehr schnell aufgefallen. Da steht ein Algorithmus drin, und zwar der RQRSQRT, der irgendwie gar nicht so aussieht, als was man eigentlich erwarten würde. Er soll nämlich den Kehrwert der Quadratswurzel berechnen. Und hin mit Zwilling, da steht er nicht drin. Stattdessen steht drin, da passiert irgendein Floating-by-Point-Bit-Level-Hacking, dann irgendein What the fuck. Und plötzlich kommt am Ende mit relativ hoher Genauigkeit das Ergebnis raus. Und das wollen wir uns mal anschauen, woran das liegt. Ja, das Ganze ist so berühmt, dass manche sich das auch tätowieren lassen wollen. Dave hat danach ja eigentlich nur noch auf Twitter gesagt, ja, das will er eigentlich nur deswegen machen, weil da, wo jemand etwas programmiert hat, auf einem Level, wo er wo nie hinkommen wird. Und ich hoffe nach diesem Vortrag werden einige hier doch sehen, dass es gar nicht so kompliziert war. Also, hoffentlich hat Steve dann doch nicht sich tätowiert. Auf jeden Fall kommt es meiner Meinung nach gar nicht auf die Konstante so sehr an, auf die sich so viele versteifen, sondern eher auf das Prinzip, was dahinter steht. Ja, worum geht es eigentlich grundsätzlich? Worüber unterhalten wir uns hier? Es geht um ein 3D-Spiel. Und in 3D werden eigentlich alle Figuren, alle Spiele, alle Grafiken, alle Szenen über Vektoren dargestellt. Da wird aus Punkten, wenn Linien hergestellt, aus Linien werden Flächen hergestellt, auf die Flächen werden alle möglichen Grafiken darauf projiziert. Um Ende sieht man das Gameplay und guckt sich das Ganze an. Wenn wir uns ein Vektor anschauen, Vektor besteht immer aus mehreren Komponenten, zum Beispiel hier der Vektor 304. Und das bedeutet, der hat in drei Raumrichtungen die drei Anteile der Länge 3, der Länge 0 in die zweite Richtung und der Länge 4 in die vierte Richtung. Das ist aber nur eine Art, wie man so ein Vektor angucken kann. Das ist nämlich der, wie er gespeichert wird. Eine andere Sichtweise ist sich, den Vektor anzugucken in der Länge, das heißt, den Betrag vom Vektor und die Richtung von dem Vektor. Und genau diese Richtung des Vektors, die man berechnet, als X geteilt durch Betrag von X, das ist das, was einfach unheimlich wichtig wird im Spiel für die Darstellung, wenn man sich Spiegelungen angucken will, die Schatten angucken will, wie projiziert man die Texturen darauf? Andauernd braucht man das eigentlich quasi für jede Fläche, muss das mindestens einmal berechnet werden. Das heißt, diese Routine wird extrem oft aufgerufen und dementsprechend war das Interesse auch so groß. Warum ist dann diese Routine so schnell und warum sieht sie so komisch aus? Dass sie erst mal so schnell ist, liegt schon mal darin, dass keine Wurzel ausgerechnet wird. Denn das Wurzel ausrechnen ist wahnsinnig teuer. Ich habe dann tatsächlich nochmal geschaut, ob ich in den alten Dokumentationen vom Co-Prozessor, vom 3680er oder vom Pentium, finden kann, wie viele Tackzyklen die laufen. Es ist gar nicht mehr so genau nachzuvollziehen. Sie dauert auf jeden Fall viel, viel länger, diese Wurzel zu berechnen, als die Additionen bzw. Subtraktionen, Multiplicationen und paar Bit-Operationen, die darin ablaufen. Und das ist der Grund, warum der so wahnsinnig schnell ist. Da wird eine Wurzel ausgerechnet, sogar ein Care-Wert einer Wurzel, ohne dass überhaupt eine Wurzel berechnet wird, sondern da gibt es nur Additionen, Multiplicationen und Bitmanipulationen. Das erklärt, warum so schnell ist, aber warum es funktioniert, ist immer noch nicht klar. Kurz zum Namen QRSQRT bedeutet einfach nur quake, reciprocal square root, also Care-Wert von der Quadratwurzel in quake. Und es kommt nicht von John Carmack, sondern ist tatsächlich schon zurückzuführen. Da gibt es Artikel drüber. Ich glaube, Silicon Graphics hat sich eine Korriffe der Grafikprogrammierung ausgedacht. Ja, Namen kann ich jetzt gerade nicht sagen, sorry. Auf jeden Fall, das Ganze war auch schon vorher bekannt. Und es gab auch schon vor der Veröffentlichung vom Source Code auch schon mathematische Anpassungen darüber. Trotzdem, cool wurde es tatsächlich erst, als es dort veröffentlicht wurde. Ah, genau, und da habe ich ja was mitgebracht. Genau, wie es so schön heißt, vom Rechenschieber zum IEEE 754. Und ein Rechenschieber, das ist sozusagen der Taschenrechner von früher. Das sieht so ungefähr aus. Und das wird uns hoffentlich erst mal erklären, wie es überhaupt zu dem Rechentrick kommt, speziell zu dem Teil mit den komischen Hexadizimientmalen Zahl, die da in dem Programm drin vorkommt. Erst mal etwas, wie funktionieren Rechenschieber. Rechenschieber heißt im Englischen schön Slidejoule, das heißt eigentlich nur verschieben von, ja, nicht Zentimeterbändern, sondern Linialen gegeneinander. Ich habe auch mal zwei Liniale gegenübergelegt. Und wenn ich jetzt zum Beispiel hier 3 plus 10 addieren will, kann ich einfach hingehen, mir die 3 raussuchen. Ich lege da das nächste Linial am Anfang an, gucke mir da die 10 raus. Und wenn ich jetzt 3 plus 10 zusammen addieren will, gucke ich dann, wo sitze ich auf dem nächsten. Da lande ich bei der 13, also 3 plus 10 ist gleich 13. Liniale addieren schlicht einfach grafisch oder mechanisch, wie man sehen will, zahlen. Und genau das passiert auch bei solchen Rechenschiebern. Macht man dort das Gleiche beim Rechenschieber, ja, ist etwas älter, deswegen sieht es schon ein bisschen gelb aus. Und man stellt dort zum Beispiel die Zahlen 3 und 10 ein. Es gibt dort die Skalen A und B, natürlich massenhaft mehr, so Rechenschieber kann viel mehr als nur zahlen addieren. Dann stellt man die 3 ein, den Anfang vom zweiten Linial, die fängt bei 1 an, gucke dann, wo komme ich bei der 10 raus, komme ich bei 30 raus. Moment, das ist keine Addition. Das ist eine Multiplication. Wie in aller Welt wird plötzlich auf diesen Rechenschieber multipliziert? Ja, ich meine, muss man sich nicht wundern. Die Scala ist offensichtlich nicht so linear, 1, 2, 3, 4, 5, sondern irgendwie so ein bisschen verzerrt. Und der Grund ist natürlich, dass die Scala so gewählt ist, dass man damit beispielsweise multiplizieren kann und viel mehr. Was dort ganz genau passiert, ist, dass dort logaritmisch gerechnet wird. Wenn ich mir jetzt beispielsweise das Produkt von 8 mal 16 ausrechnen will. Und ich stelle jetzt 8 und 16 als Zweierpotenzen dar. Also alle Logritmen, die jetzt hier drin vorkommen, meine ich meistens Zweierpotenzen mit, obwohl es spielt keine genauer Rolle. Auf jeden Fall hat 8 die Zweierpotenz 2 auch 3 und 16 die Zweierpotenz 2 auch 4. Wenn ich die beiden miteinander multiplizieren will und ich schreibe das aus, dann sehe ich hier, ja, ich kann die ganzen Zahlen natürlich auch einfach nur hintereinander schreiben und sehe dann, ja, das ist das Gleiche wie 2 hoch 3 plus 4, also 2 auch 7. Und da sieht man aus der Multiplication. Unten wird oben in Exponenten eine Addition. Und ja, wenn ich dann gucke, was kommt dort auch wieder heraus als Ergebnis, halte ich die 128, das ist gerade 2 auch 7. Und was dort oben steht, eben Exponenten, das ist ja gerade der Logritmus der Zahl unten. Und in dem Sinne, viele kennen es wahrscheinlich schon aus der Schule, das sind die Logritmengesetze. Der Logritmus macht aus einer Multiplication eine Addition. Ja, das sind die Logritmengesetze, die vielleicht viele schon kennengelernt haben, hoffentlich. Wenn ich also Logritmus von A mal B habe, ist es einfach Logritmus A plus Logritmus B, dementsprechend Logritmus von A durch B, das ist Logritmus A minus B. Und das werden wir jetzt noch brauchen. Logritmus von A hoch P ist P mal Logritmus A. Gucken wir uns noch mal an, wie das da genau abgelaufen ist auf dem Rechenschieber. Als ich dort die 3 zur 10 geschoben habe, habe ich in Wirklichkeit eine Position Logritmus von 3 eingestellt, dann Logritmus von 10 verschoben. Und was kommt für ein Ergebnis raus? Das ist gerade Logritmus von 30 bzw. die Stelle von Logritmus von 30, wie das auf der Skala auf dem Rechenschieber steht. Und wenn ich es dann ablese, dann mache ich quasi wieder diese Exponentation und komme dort zum Ergebnis 30. Deswegen habe ich dort 3 mal 10 gerechnet anstatt 3 plus 10 auf dem Rechenschieber. Ja, und das ist eine Art, wie ich mit Logritmus einfach viel einfacher rechnen kann. Und genau das passiert auch in dem Algorithmus. Fangen wir einfach an. Man kann mit dem Logritmus auch wunderbar einfach quadrieren. Wenn ich also oder hier potenzieren, wenn ich 4 hoch 3 rechnen will, dann haben wir gesehen, wenn ich das hinter den Logritmus schreibe, kann ich daraus 3 mal Logritmus 4 machen. Das heißt, ich muss nur den Logritmus von 4 berechnen, zum Beispiel den 2 Logritmus, das ist dann gerade 2, bekommen wir raus, 3 mal 2 ist 6. Wenn ich dann wieder exponentziere, das heißt 2 auch 6 berechnen, kommt die 64 raus. Das ist das, was ich erwartet habe, als ich 4 hoch 3 rechne. Ja, wenn ich potenzieren kann ganz zahlig, dann funktioniert, dass potenzieren auch mit gebrochen rationalen Zahlen. Denn Wurzel ziehen, die Quadratwurzel, ist nichts anderes als hoch ein halb zu nehmen. Wenn ich hoch ein halb nehme und ich mache das im Logritmus, bedeutet das auf der anderen Seite nur mit ein halb mal zu nehmen. Das heißt, Wurzel ziehen wird im Logritmus plötzlich vollkommen einfach. Ich muss nur die Zahl halbieren. Genau das werden wir nachher auch sehen. Wenn ich also die Wurzel, Quadratwurzel aus 64 nehmen möchte, wiederum die 2 Logritmus von 64 ist 6, die Hälfte davon ist 3, also ist unser Ergebnis 2 auch 3, 8 ist tatsächlich die Quadratwurzel von 64. Damit kommen wir schon mal mit dem Logritmus gut zurecht um Wurzeln auszurechnen. Natürlich können wir auch und das brauchen wir, die Kehrwerte von Wurzeln berechnen. Kehrwert bedeutet hoch minus zu nehmen, wenn ich also Kehrwert von Wurzel nehmen will, hoch minus ein halb. Im Logritmus läuft es wieder ganz genauso ab. Ich nehme den Logritmus von der Zahl, multipliziere die Zahl mit minus ein halb, exponentiere das Ganze wieder mit 2 hoch und komme zum Ergebnis, dass also 1 durch Wurzel 64, das Gleiche ist wie 2 auch minus 3, also 1 durch 2 hoch 3, 1 durch 8, ein Achtel ist das Ergebnis von 1 durch Wurzel 64. Das ist erstmal sozusagen dies zum Aufwärmen, was dort im Logritmus passiert. Wir werden in dem Logritmus sehen, dass wir dort mit Logritmengesetzen rechnen werden und wer schon ein bisschen reingeschaut hat im Logritmus sieht dort nachher ein Schiff nach rechts. Schiff nach rechts ist durch 2, wir müssen das noch in Vorzeichen wechseln, ist exakt die Rechnung, die man hier sieht, das, was von möglicher Schule oder im ersten Semester im Studium auch gemacht würde. Tatsächlich Quadratwurze ziehen, bevor wir Computer hatten, ja, wurde ganz viel mit Tabellenwerken gemacht. Die ganzen Logritmen, da gab es dicke Bücher drin, wo einfach nur Logritme drin stand und hat mal von nachgeschlagen, was ist der runde Logritmus von der Zahl, hat dann halbiert, gedrittelt oder irgendwas und wieder zurücktransformiert, indem man guckt, wo kommt die Zahl wieder in vor, um es wieder zu exponieren. Das war die Art, wie früher genau gerechnet wurde, dass ja, abschätzen, hat man halt eben mit dem Rechenschieber gemacht. Wo kommen nun die Logritmen her? Denn einerseits zu verstehen, warum wird dort irgendwo halbiert und minus genommen, ist die eine Sache, aber irgendwo muss ja ein Logritmus sein und normalerweise ist Logritmus berechnen nicht weniger kompliziert als die Wurze zu berechnen. Ja, und da kommen wir jetzt zur IT. Wie speichert der Computer Zahlen? Ganz generell, viel haben Sie schon mitbekommen, der Computer, außer Analogcomputer natürlich, rechnet normalerweise mit den Zahlen 0,1 und weil mit den Zahlen 0,1 irgendwie ein bisschen wenig auszudrücken ist, muss man das natürlich versuchen, irgendwie ein bisschen zu erweitern. Man fügt mehrere Zahlen zusammen und kann damit natürlich auch größere Zahlenbereiche darstellen. Meistens guckt man sich das nur für die ganz zahligen Zahlen anders, geht genauso weiter auch in die Komma-Zahlen. Wir benutzen dann ein sogenannte Stellenwertsystem, wo wir jeder Position der Ziffer eine Wertigkeit zuordnen. Wenn ich also hier jetzt so eine Zahl habe wie 1,0, 1,0, 1,0, 1,0, dann hat jede dieser Zahl einen Stellenwert und man fängt direkt vom Komma an. Dort ist wie im Zehnersystem der Stellenwert 1, weil 10 hoch 0 ist 1. Die Zahl davor, den Stellenwert 2, weil 2 auch 1 ist 2. Und sie geht immer weiter, dritter, vierter und genauso läuft es auch weiter hinter dem Komma. Dort werden die Stellenwerte dann negativ gezählt. Das heißt, die erste Ziffer hinter dem Komma hat den Stellenwert minus 1. 2 hoch minus 1 ist ein halb. Die nächste hat minus 2, 2 hoch minus 2 ist ein viertel. Die nächste hat minus 3, 2 hoch minus 3 ist ein achtel. Und dahinter kommt die 2 hoch minus 4, das ist ein sechzehntel. Und wenn ich jetzt meine Zahl, die ich gerade genannt hatte, die ist 1,0, 1,0, 1,0, 1,0, 1,0 ausrechnen möchte, was bedeutet das eigentlich in Dezimal? Da muss ich einfach nur die Stellenwerte, wo ich eine 1 habe, zusammenzählen und komme dann hier zu dem Ergebnis 18,625. Und für diejenigen, die noch nichts vom Schiften gehört haben oder verschieben von BITS, wollte ich das hier auch nochmal ganz kurz demonstrieren, wenn ich jetzt so eine Zahl nehme in der Binärdarstellung und ich verschiebe alle Ziffern, zum Beispiel um eine Stelle nach rechts, dann bekommt jede Ziffer genau den Stellenwert, der durch 2 geteilt ist. Und damit dividiert sich die gesamte Zahl auch durch 2. Das heißt, einmal nach rechts verschieben, rechts, für die Zuschauerinnen, muss ich durch 2 addieren, einmal nach links verschieben, multipliziere ich mit 2. Wenn ich 10 mal verschiebe, nach links ist es mal 2 auch 10, also mal 1024. Das ist etwas, was die Computer wahnsinnig schnell können, also Multiplikation und Division mit 2 ist nur verschieben von BITS ein Taktzyklus fertig, schnellste Operation, die wir haben. Ja, aber die Art der Darstellung ist nicht so praktisch, das kennen wir schon mal aus unserem Leben. Wir werden nicht alles in Kilogramm ausdrücken, wenn wir jetzt mal vorstellen, die ever given. Das ist ein Schiff, das ist ein halbe Megatonne schwer, also bis zu hat vielleicht Sand geladen und Sandkorn, es sind vielleicht ein 200-Mügramm ist ein Sandkorn schwer, da sieht man schon, was es für ein Spektrum hat mit Zahlen, mit dem wir uns auseinandersetzen. Dafür benutzen wir natürlich gerne die wissenschaftliche Schreibweise, die nicht nur in dreier Potenzen immer wieder neue Worte sich ausdenkt, sondern immer einzeln die Potenz hinten ran schreibt. Und genau das passiert auch im Binärbereich, genauso wie wir bei Dezimalzahlen so eine wissenschaftliche Notation benutzen können, können wir das auch mit Binärzahlen machen. Moment im kleinen Unterschied, einen kleinen Knift, der das Ganze noch ein bisschen effizienter macht. Bei der wissenschaftlichen Schreibweise im Dezimalsystem verschieben wir so lange, dass Komma typischerweise nur noch eine Zahl vom Komma stehen bleibt. Da haben wir jetzt hier 65535 ist dann gerade 6,5535 mal 10 noch 4. Machen wir das im Binärsystem stellt man fest, hola, es gibt ja nur die Zahlen 1 zu 0. Das heißt, ich verschiebe meinen Komma so lange, bis eine 1 vorne steht. Da steht immer eine 1 vorne. Egal, welche Zahl ich dort hatte, außer der 0. Das ist so eine Eigenschaft, die der Computer sich zu Nutzen macht. Diese erste Zahl hier vorne, die lässt er beim Speichern weg. Außer bei der 0, das ist ein Sonderfall, da kommen wir gleich dazu, aber in den ganzen anderen Fällen, die auftreten können, lässt man das typischerweise weg, weil es nicht gespeichert werden muss. Und dann kommen wir jetzt zum Standard, wie eigentlich so gut, wie alle Rechner, alle Programme, ihre Fliesskommas, ihre Kleidflummerzahlen speichern. Das ist der IEEE 754 und da sehen die Zahlen immer so aus, dass immer ein Bit fürs Vorzeichen reserviert ist. Dann kommen einige Bits für den Exponenten, das ist das, was beim 2 hoch sowieso dahinter steht. Und dann kommt die Matisse, das sind alle Zahlen, die davor stehen bis auf die erste Zahl 1. Und die verschiedenen Datentypen IEEE 754 unterscheiden sich jetzt nur darin, wie groß sozusagen dieses ganze Paket ist, wo ich die Zahl hinein stecke, ob es 16, 32, 24 oder 128 Bits sind und wie aufgeteilt wird, wie viele Bits dort für den Exponenten und für die Mantisse vorhanden sind. So gibt es das Format HAF, das passt in 16 Bits rein. Da sind für den Exponenten 5 Bits vorgesehen und für die Mantisse 10 Bits. Und wenn man sich damit überlegt, was kann ich denn da jetzt für Zahlen mit ausdrücken, dann sieht man, okay, die kleinste Zahl über 0 natürlich jetzt ist plus minus 10 noch 7 und die größte Zahl 10 noch 5. Das heißt, man kommt mit dem HAF-Format nicht so weit. Und tatsächlich, weil die Mantisse auch so klein ist, hat man nur so ungefähr drei signifikante Stellen, die man überhaupt auswerten kann. Das wird im meisten Programmiersprachen gar nicht angeboten. Außer Speziellen, die heute eigentlich sehr, sehr wichtig geworden sind, da kommen wir nachher noch zu. Die eigentlich mit am häufigsten benutztem Typen sind HAF und Double, oder die Single und Double. Single ist der Daten-Typ Float, der ist 32 Bit groß, mit dem arbeiten wir auch hier und Double ist eben 64 Bit. Und entsprechend haben die ja eigentlich signifikante Stellen 7 und 15, ich meine immer 8 und 16 und man hat einen bestimmten Zahlenbereich, dadurch, dass man diese wissenschaftliche Schreibweise benutzt. Das macht sehr viel Sinn, man kann darin wunderbar rechnen. Und würde man jetzt zurückrechnen, was für eine Zahl steht eigentlich dahinter, ist es genau die Art, die wir vorhin gesehen haben. Man hat dieses erste Bit, das beschreibt einem, was im Vorzeichen man hat. Man denkt sich in fast allen Fällen vorne eine 1 davor, dann werden die ganzen Bits dahinter geschrieben, die in der Mantisse drin stehen. Und dann kommt 2 hoch, alle Bits, die im Exminenten stehen, minus den Bias. Das ist etwas Besonderes. Die Zahl wird also nicht gespeichert in dem Format, in dem normalerweise vorzeichenbehaftete Zahlen im Rechner gespeichert werden, das ist das Zweierkomplement, sondern die Zahl wird einfach schlicht verschoben um einen bestimmten Wert, nämlich genau um den Bias. Der liegt auch genau im Mitte des Zahlenbereichs normalerweise, den Exminenten annehmen kann. Aber das ist in diesem Fall wichtig. Der Grund ist dafür, dass sich die Leute, die sich in der IJPI 754 ausgedacht haben, das Format so wählen wollten, dass man Zahlen miteinander vergleichen kann, ohne dass man sie erst dechiffrieren muss, also wieder zurückrechne, was da eigentlich drin steht. Und das ist der Grund, warum diese komische Bias-Korrektur drin vorkommt. Und diese Bias-Korrektur ist übrigens auch der Grund, warum wir diese komische Hexadizimale Zahl dort im Kot drin stehen haben, wie wir gleich sehen werden. Aber jetzt möchte ich kurz noch erklären, was passiert eigentlich mit der Null? Dieser Fall, dass man dort die Einzigen denkt, das ist einfach der normale Fall, der normalisierte Fall. Da gibt es auch die denormalisierten Zahlen. Das ist, wenn der Exminent nämlich gerade Null ist, dann schreibt man vorne eine Null hin, das ist so ein Sonderfall, um auch alle anderen Zahlen darstellen zu können. Damit kann man auch noch weiter in die noch tieferen Zahlen kommen. Das ist auch der Grund, warum die nicht ganz symmetisch sind. Man kommt bis 10 hoch 38, aber bis 10 hoch minus 44. Da kriegt man halt ein paar bits mehr, aber die Anzahl der signifikanten Stellen bricht natürlich dabei ab. Und dann gibt es noch weitere Sonderfälle, wenn der Exminent komplett nur aus Einzen besteht. Damit wird im ITPI-Standard, werden so besondere Zahlen wie Inf, Nann und alles möglich gekodiert. Das macht bei den normal verwenden Datentypen kein Problem, wie beim Float und beim Double oder beim Long Double. Aber bei den sehr sparsamen Datentypen ist es ein riesiges Problem, dass man in diesem Fall hier, da hat man ja nur 32 Exminenten, die es gibt, ein ein 32. Zahlenbereichs für irgendwelche Konstanten wegwirft, das ist nicht mehr zeitgemäß. Kommen wir nachher noch zu. Kleines Beispiel. Wenn man jetzt eine Zahl mal ins Vizekommerformat umrechnen möchte, die ist zum Beispiel gegeben als, sorry, ich habe direkt vom Vortrag noch recht viel entdeckt, 6C80 in Hexadezimal. Hexadezimal hat den Vorteil, dass immer eine Ziffer genau vier Binärziffern umfasst. Die 6 ist, als ich hier dargestellt, dann ein 0, 1, 1, 0. Das ist für 1, 2, 4. 2 plus 4 gibt 6. Deswegen ist das hier die Darstellung in der Bits für die 6. C ist gerade 12, deswegen haben wir 8 plus 4, 1, 1, 0, 0. Die 8 hat nur das oberste Bit gesetzt, 1, 0, 0, 0. Und die 0 ist hinten dran. Wenn ich das jetzt mir mal auf diese Form bringe, dann kann ich nachrechnen, dass sozusagen ich einen Exponenten habe, 26. Von der 26 muss ich dann die 15 wieder abziehen. Das heißt, ich bekomme in meiner Darstellung die 2 auch 11 als Exponenten. Und in der Mantisse schreibe ich vorne die 1 mit dran, ansonsten übernehme ich die restlichen Ziffern hier. Da habe ich 1, 0, 0, 1. Im Binär, das wird in Dezimal 1, 1, 2, 5. Wenn ich das ganze zusammen zähle, komme ich bei 2304 raus. Das heißt, man kann wunderbar aus dieser Binärdarstellung umrechnen. Das macht der Computer natürlich intern, wenn er damit rechnet. Aber der spannende Teil ist jetzt hier, was wäre denn jetzt der Logarithmus davon? Der Logarithmus der Zahl 2304 ist ungefähr 11,17 Moment. Die Zahl haben wir doch schon mal irgendwo gesehen. Das ist genau der Exponent hier oben. Da steckt der Logarithmus drin. Und natürlich muss er da drin stehen, weil im Grunde genommen habe ich bei dieser Darstellung immer ein 1, irgendwas mal 2 auch irgendwas. Und wenn ich den Logarithmus rechnen will, den 2er Logarithmus, will ich die Zahl als 2 auch irgendwas darstellen, dass irgendwas da oben interessiert mich. Und im Grunde genommen habe ich das da schon komplett stehen. Das heißt, der Logarithmus dieser Zahl, so ungefähr, steht dort in dem Exponenten drin. Das heißt, genau da ist der Exponent versteckt, den wir benutzen können, um unsere erste Nährung für 1, Wurz und X zu bestimmen. Und das ist die Bitmanipulation, die da drin passiert. Was nämlich gemacht wird im Algorithmus, ist einfach aus diesem Float diese Bits zu separieren, diese als ein Interesse zu benutzen, dann damit zu rechnen, es durch 2 zu teilen, zu negieren und wieder zurückzuschieben, die uns dann hilft, die erste Nährung für 1, Wurz und X zu bestimmen. Wenn ich also jetzt mir die Zahl hier mal nehme, zum Beispiel diese 6C80 und ich mache jetzt ein sogenanntes Type-Punning, das heißt, ich interpretiere diese Zahl, die eigentlich ja eine Fleece-Kommerzahl ist, als ein Integer, was man beim Programmieren so gut wie nie tun sollte, weil das natürlich gegen jede Typ-Konvention verstößt, außer man weiß ganz genau, was man tut, was hier der Fall ist. Wenn ich das jetzt durch, was ist hier, 2 auf 10 Teile, denn hinten sind genau 10 Bits dahinter. Wenn ich also jetzt durch 10 Teile mache ich ein Schiff, 2 auf 10 Teile mache ich ein Schiff und 10 Bits, habe also alles hinten dran weggeschoben, dann habe ich nur noch den Exponenten dastehen. Und wenn ich den dann normal ausrechnen, dann bekomme ich dort tatsächlich 11,875 in diesem Fall heraus. Die 875 sind einfach so die Überreste von der Mantisse. Tatsächlich kommt das dann sogar dem echten Logrythmus sogar noch ein bisschen näher, wenn man das macht. Im Algorithmus spielt aber der Nachkommateil keine Rolle, weil nur Integer gerechnet wird aus Geschwindigkeitsgründen. Aber wir sehen hier, in diesem Exponenten steht der Logrythmus bis auf eine Zweipotenz, also bis eine Ganzzahl in der Zweipotenz drin. Und das ist der riesengroße Vorteil, warum man das machen möchte. Ja, jetzt habe ich überlegt, in welcher Sprache illustriere ich das kurzmal. Es gibt ja verschiedene Varianten, ob in Python, Java, aber naja, manche schauen sich das ja hier auch im Browser an. Und da ist natürlich JavaScript das nächste. Eigentlich in jeder Programmiersprache muss man sich ein bisschen verrenken, um das so genannte Type-Punning zu machen. In JavaScript sieht es so aus, dass ich einen Airbuffer erzeuge. Ich schreibe in diesen Airbuffer auf der einen Seite mein Float rein. Auf der anderen Seite hole ich mein 32-Bit-Int raus. So mache ich das Type-Punning. Da muss man in allen möglichen Sprachen außer in C eine gewisse Verrenkung machen. Gibt es aber auch für Haskell und alle möglichen anderen Sprachen. Es gibt auch für Rust Möglichkeiten das zu tun, natürlich alles verboten. Aber wenn ich jetzt sozusagen mir einen Algorithmus schreiben möchte, der mir aus einem Float dementsprechend den ungefähren Logrythmus ausrechnet, ist es nach dem Type-Punning, wo ich einfach so dieses Rauskopieren mache, eigentlich nichts anderes mehr als ein Teil durch zwei auch 23, beim Float jetzt nicht mehr 10, sondern 23 Bitmantisse. Und nochmal 127 abgezogen, das ist nicht genau dabei. Wenn ich das ganze mal für 2304 ausrechne und das Ausdruck, die 2304, gibt mir einen ungefähren Logrythmus von 11,125, den zweier Logrythmus, weil 2048 ist gerade 2 auch 11, der tatsächlich wäre 11,1699 gewesen. Also eigentlich funktioniert schon mal, dass man rechner dran kommt. Es ist sogar wegen der Mantisse sogar ein bisschen genauer, als hätte man nur den ganz haligen Wert. Tatsächlich geht es aber nicht darum, dass wir den Logrythmus ausrechnen, sondern wir wollen mit dem Logrythmus rechnen. Wir wollen mit dem Logrythmus nicht nur rechnen, sondern einst durch Wurzel X ausrechnen, als einfaches Beispiel erst mal um warm zu werden, möchte ich erst mal nur den die Wurzel ausrechnen. Das heißt, dazu mache ich erst mal einen Type-Punning, holen mir also meinen Integer daraus. Ich ziehe mir meinen Logrythmus aus der Zahl, indem ich um 23 Bitz nach rechts verschiebe, wir sind ja beim Float, die Mantisse ist 32 Bitz lang, also 23 Bitz nach rechts verschieben, ich ziehe den Bias ab und dann habe ich in i mein Exponenten drin stehen. Den muss ich jetzt nur durch zwei teilen und wenn ich jetzt wieder wissen will, was für eine Zahl kommt raus, wieder zurück schieben, das ist das Exponentieren. Das heißt, ich multipliziere die Zahl, addiere wieder mein Bias, die 127, schifte wieder um 23 Bitz nach links und mache daraus wieder umgekehrt, ich schraub sie in den Baffereien als Int, hole es mir als Float raus und ich gebe es raus und dann sehe ich hier die Wurzel aus, 17 ist in diesem Fall 4, in Wirklichkeit wäre es 4,1 noch was gewesen. Funktioniert also ganz gut. Sache es nur, wenn man sich das anschaut, merkt man, moment mal, diese ganzen Operationen hier, die könnte ich mir doch bestimmt ein bisschen schicker verkleiden. Und genau so machen wir mal machen. Was machen wir denn hier? Wir teilen i durch 2 auch 23, wir ziehen 127 ab, wir teilen durch 2, addieren 127 und multiplizieren wieder mit 2 auch 23. Gut, diese minus 127, die können wir erst mal erst nach draußen schieben. Wenn wir die nach draußen schieben, aus dem durch 2 heraus, bleibt natürlich 127 halbe übrig und hier habe ich i geteilt ich 2 auch 23, geteilt ich 2, daraus wird i geteilt ich 2 auch 24 oder 24 Bit nach rechts verschoben und ich habe hier hinten minus 127 halbe plus 127, die kann ich beide miteinander zusammenzählen, kommt 127 halbe raus und das Ganze wird am Ende ja wieder mit 2 auch 23 mal genommen. Na gut, einmal die durch 2 auch 24 mal 2 auch 23 ist einfach eingeteilt durch 2 und die 127 durch 2 mal 2 auch 23 ist 127 mal 2 auch 22. Und wenn wir das jetzt mal in Zahlen umrechnen oder direkt hinschreiben, ist es eigentlich i durch 2 plus 127 um 22 Bit nach links verschoben, was wir direkt auch ausrechnen können, würde ich das in den Compiler schreiben, würde der Compiler das auch direkt so tun, aber im Code wurde ja entschieden, nee, ich schreibe mal lieber also ein bisschen cooler die Hexadismalzahl hin, wenn ich das mache, bin ich bei 0x1fc000, das sind die ganzen Nullen hinten wegen dem ganzen Bit, die reingeschoben wurde und vorne, jetzt muss ich ungefähr schreiben, statt geteilt durch 2 mache ich jetzt den Bit Shift, da verliere ich das letzte Bit, ich rechne also nicht mehr mit Kommazahlen, deswegen ungefähr. Und jetzt sehen wir schon, wie diese Struktur zusammenkommt von der komischen Hexadismalzahl, in die in dem Algorithmus drin vorkommt. Denn genau das gleiche können wir natürlich jetzt auch in unserem Code schreiben, wir sehen hier schon fast genau die gleiche Struktur, nur dass jetzt hier kein Minus drin vorkommt. Klar, wir haben ja auch noch nicht durch die Wurzel geteilt. Wenn wir das ganze, ganz kurze Sache, das Ergebnis ist ganz plisziern anders als vorher, weil wir jetzt halt eben diese Rundung drin haben, ein bisschen anders rechnet der dann schon, aber alles in dem Rahmen, wo wir sagen, wir sind eh nicht so genau. Wenn wir tatsächlich jetzt mal unser R-Square-Root ausrechnen wollen, also Kehrwert der Quadratwurzel, dann müssen wir auch wieder unseren Logrythmus extrahieren. Wir müssen ihn durch zwei Teilen und negieren, hier durch zwei geteilt, negiert. Dann erdieren wir wieder 127 und schiften es wieder und danach der Rest ist wieder genau gleich. Ja, da sehen wir, im Grunde genommen sieht das Programm genau gleich aus, kann aber dann plötzlich aus der 17 den Kehrwert von der Wurzel berechnen. 17 Wurzel ist ungefähr 4, Kehrwert davon ist ein Viertel 0,25. Exakt der Ergebnis wäre 0,24, wir haben 0,25, weil das ja nur eine Nährung ist. Auch das lässt ich wieder zusammenfassen. Wir haben hier wieder den Teil, wo wir erstmal D schifrieren. Diesmal negieren wir aber noch und müssen dementsprechend, wenn wir es nach draußen ziehen, da habe ich, glaube ich, einen kleinen Fehler, das müsste schon, nee, genau, hier ist noch, ich habe genau, ich habe das anders so gemacht, ich habe die ein halb nach drinnen gezogen, deswegen haben wir hier noch minus 127 halbe stehen, die durch zwei ist drin. Wenn ich jetzt die Klammer aufhebe, das heißt minus rausnehme, komme ich bei minus i, geteile ich zwar auch 24 raus und ich kann die 127 mit 127 halbe addieren, weil aus minus und minus ergibt es Plus. Das Ganze wieder aufgelöst, die Klammer wieder rausgeworfen, bin ich bei 381 mal 2 auch 22 geteilt durch i durch 2. i durch 2 ist i einmal nach rechts geschiftet ungefähr und hier vorne komme ich bei der Konstantin 0x5f4 und 5 0 hinten ran raus. Exakt das steht im Kot drin. Nein, fast, im Kot steht tatsächlich drin 0x5f3759df und ganz viele wundern sich, warum steht diese komische Zahl da? Ja, im Grunde genommen kann man jetzt einfach eine Brute Force suchen machen, denn wir wissen ja schon, dass wir gar nicht so genau sind. Der Lorythmus wird ja ein bisschen höher sein als unsere Zahl. Das heißt eigentlich müssen wir da was draufziehen, das müssen wir hinten wieder rauszielen. Dann ist es auch noch so, dass hinten auch noch ein Algorithmus hinten dran kommt, der auch noch weiter arbeitet mit der Zahl. Wenn man alles zusammenzählt, kann man einfach durch eine Brute Force suche, einfach Zahlen finden, die im Schnitt ein klein bisschen besser sind und so kann man dann auf eine Zahl durchprobieren, schlichtweg dieses 0x5f3759df, wer sich da ein bisschen genauer für interessiert, das ist eine Arbeit von M. Robertson, der hat das in der Bachelorarbeit mal geschrieben, hat da ganz viel ausprobiert und auch eine Brute Force suche darüber gemacht und gezeigt, ja, was kriegt da alles für Ergebnisse raus, kann man das alles genau nachlesen, aber dass die Zahl genauso aussieht, ist einmal gar nicht so wichtig, weil die Unterschiede sind da gar nicht mehr so groß und die andere Sache ist, ja, es hängt viel halt auch an dem Algorithmus, den man hinten dran hängt. Trotzdem ist die Zahl natürlich etwas Besonderes, deswegen ist sie natürlich auch so bekannt geworden. Ja, wenn wir es dementsprechend hinschreiben und mit dem Algorithmus jetzt unser R-Square-Root ausrechnen, sieht man, kommen wir auch sehr nah an unsere Zahle heran und das ist exakt das, was dort im Algorithmus dann auch passiert, okay, mit einer gleich anderen Wurzel. Aber dann kommen wir zum zweiten Teil des Algorithmus. Dort passiert jetzt eine Iteration, die unsere bisherige Geschätzung richtig genau macht. Ja, und das ist ein Nütenverfahren. Das Problem, was wir hier grundsätzlich haben, ist, dass wir hier nicht lineare Gleichung lösen wollen. Dieses einzig Wurzel X ausrechnen kann man bezeichnen als das Lösen einer nicht linearen Gleichung, weil es ist keine gerade oder so, das kann man nicht einfach so ausrechnen. Und deswegen gibt es ein paar Standardverfahren, wie man davor gehen kann und eines der Standardverfahren, das hier auch benutzt würde, ist das Nütenverfahren. Das Nütenverfahren benutzt man aber, um Nullstellen zu finden. Naja und Ausrechnung von der Quadratwurzel und Kehrwert davon ist nicht gerade eine Nullstellen Suche, das heißt, wir müssen es erst mal umwandeln. Kurz mal dazu, was ist das Nütenverfahren? Nütenverfahren, wie gesagt, sucht Nullstellen. Kann man sich auch ganz leicht vorstellen, was da passiert. Wenn ich jetzt hier irgendeine Funktion habe, die irgendwie so aussieht und hier eine Nullstelle hat und ich will die finden, ich weiß aber nicht, wo sie ist, aber ich kann die Funktion auswerten. Kann man an einer Stelle mal anfangen, beispielsweise an der Stelle 3. Dann gucke ich mir an, was habe ich für einen Funktionswert und wenn ich die Funktion kenne und deren Ableitung bestimmen kann, wie bei uns hier in dem Fall, kann ich dann eine gerade da durchlegen und dort gucken, einfach direkt ausrechnen, wo schneidet diese gerade X-Achse und nehme das als nächste Nährung. Nehme dort wieder den Funktionswert, die Ableitung macht die nächste gerade, hat mein nächsten Funktionswert und so weiter und auf die Art und Weise komme ich immer den nächsten Schritt heraus. In diesem Bild habe ich aber gemogelt, ich habe hier eine Funktion genommen, wo es richtig schlecht funktioniert. Normalerweise konvegiert das Nütenverfahren so schnell, dass jeder Versuch es irgendwie so aufzuzeichnen, fehlschlägt. Also das hier ist ein Beispiel, wo Nüten eigentlich keine gute Chance hatte. Formal aufgeschrieben, dieses Lösen dieser gerade gleichen und die Nullstellen finden ist einfach genau diese Formel. Immer wenn ich den nächsten Schritt finden will, nehme ich meinen alten Schritt und teile den Funktionswert durch die Ableitung der Funktion. Und typischerweise ist es so, ist man schon nah an der Nullstelle oder an der Lösung, der nicht in einer Gleichung, dann konvegiert es verdammt schnell, ist man weit weg, geht es manchmal gar nicht. Und das ist ein Problem. Was hier also typischerweise gemacht wird, um so eine Gleichung zu lösen und zwar wir suchen die Zahl Y, die 1 durch Wurzel A ist, dann muss ich daraus als meine Nullstellen Formulierung machen, womöglich ist keine Wurzel mehr drin vorgekommen, weil was bringt mir eine Interaktion, wo ich eine Wurzel am Ende ausrechnen will, wenn ich selbst wieder eine Wurzel benutze. Und der Standardweg, den man hier wählt, ist einfach die Gleichung einmal zu quadrieren. Dann steht dort, dass Y² gleich 1 durch A ist. Und wenn ich jetzt einfach das 1 durch A auf die andere Seite bringe, dann habe ich Y² minus 1 durch A gleich 0. Eine Nullstelle, die ich jetzt suche. Und damit kann ich eine Funktion definieren. Er von Y ist gleich Y² minus 1 durch A und sagen, ich möchte von dieser Funktion die Nullstelle haben. Und wenn ich diese Nullstelle finde, dann habe ich die Stelle gefunden, die mir 1 durch Wurzel A ergibt. Ich muss also mir jetzt die Ableitung überlegen. Da bei F von Y hier und nur das Y² drin vorkommt, ist die Ableitung gerade 2 Y. Und wenn ich das jetzt in meine Formuleinsätze fürs Nütenverfahren, dann bekomme ich Yn plus 1 ist gleich Yn minus, über dem Buch steht unsere ursprüngliche Funktion, Yn² minus 1 durch A, geteilt durch 2 Yn unserer Ableitung. Das bringt man darauf eine schöne Reform. Das ist die Art, wie man das normalerweise dann ausrechnet, Yn durch 2 plus 1 durch Yn mal A mal 2. Das ist die Art, wie man es normalerweise rechnen würde. Und man sieht das schon, wie die sieht diese Funktion aus. Ja, es ist zu erwarten, dass ich normalerweise sobald ich einen Punkt in der Nähe habe, sehr schnell mich dem Nullpunkt nähe, außer ich starte an dieser Stelle, da habe ich die Risikprobleme, wenn ich sozusagen bei der Null nahe bin, dann haut es mir ab und ich werde keine Nullstelle bekommen. Ich habe es einfach mal für zwei Werte ausgerechnet. Einmal habe ich mir die Zahl 2 genommen und ich will also ausrechnen, was es 1 durch Wurzel 2 und einmal was es 1 durch Wurzel 8 und starte einfach mal bei 1. Und man sieht sozusagen nach dem ersten Schritt habe ich noch 0,6 Prozent Fehler. Im zweiten Schritt 0,17 Prozent relativem Fehler und drittens Schritt 0,0002 Prozent Fehler. Man sieht, dieses Verfahren konvegiert extrem schnell. In dem Fall, wo ich die 1 durch Wurzel 8 rechnen will, geht es nicht ganz so schnell. Trotzdem bin ich nach dem dritten Schritt schon richtig gut dabei. Ja, und da kann man sich trotzdem jetzt fragen, OK, wenn das Verfahren so gut ist, man sieht hier pro Schritt verdoppelt sich die Anzahl der signifikanten Stellen, um das Doppelte, also verdoppelt sich immer die Genauigkeit dort. Warum hat er denn das nicht benutzt? Naja, man sieht in dem zweiten Fall, es hängt von meinem Startwert ab. Ich habe hier 1 als Startwert genommen. Das ist beim zweiten nicht so gut, deswegen konvegiert es nicht so schnell. Ja, und ich teile durch eine Zahl und Division ist nicht ganz so schlimm wie Wurzel ziehen, aber auf den alten CPUs war das auch ein bisschen langsamer. Deswegen, wahrscheinlich deswegen hat er sich überlegt, OK, das mache ich nicht so. Wobei, das ist der Standardweg, sowas zu machen und auch wirklich die beste Art, es zu tun. Trotzdem hat er sich überlegt, OK, ich will dieses Teil nicht mit drin haben. Wie kann ich das vermeiden? Und die Trick, die er angewendet hat, ist, dass er die Gleichung, die wir vorhin als Ursprung genommen haben, einfach nochmal ein Kehrwert davon gebildet hat. Also anstatt Y Quadrat gleich 1 durch A zu lösen, 1 durch Y Quadrat gleich A sich anzuschauen. Ich bin das auf die andere Seite, habe wieder eine Nullstellenformulierung und muss dann dementsprechend für diese neue Funktion f und y gleich y noch minus 2, minus A die Ableitung ausrechnen und das in unsere Formel einsetzen. Schon mal gleich vorne weggesagt. Die Funktion sieht so aus. Die hatten Pol bei Null und wenn ich irgendwo hier mal starte, sieht man, ich werde irgendwo ganz falsch landen. Das ist keine nette Funktion mehr. Und dementsprechend werden wir sehen, dass die nicht so gut funktioniert. Aber da wir geteilt durch y hoch minus 3 teilen, kriegen wir hier ein y auch 3 nachher raus. Wir können das Ganze komplett ohne Division nachher rechnen, weil die Division, die da eigentlich noch drin war, kommt durch die Division nochmal durchgeführt. Ja, fällt es einfach heraus. Probieren wir es mit den gleichen Testwerten aus. Sehen wir, ja, wir kriegen auch die 1 durch Wurzel 2 ausgerechnet, aber deutlich langsamer und mit deutlich schlechteren Werten. Ja, bei 1 durch Wurzel 8, da versagt das Verfahren komplett. Das heißt, wir haben jetzt ein schnelleres Verfahren, das langsamer konvergiert, wäre also allein keine Lösung. Und ja, die Kombination macht es. Die Kombination ist das, womit der Algorithmus eigentlich erst funktioniert. Denn was der Algorithmus jetzt eigentlich tut, ist, dass er die Annäherung über den Algorithmus nutzt, als ersten Schritt, um erst mal nahe der Nullstelle zu kommen, um dann im zweiten Schritt die schlechtere Nulliteration zu benutzen, um dann zum richtigen Ergebnis zu kommen. Und wenn man sich das mal anguckt, hier jeweils der nullte Schritt ist jeweils hier die Rechnung über den Algorithmus und Schritt 1 und 2 sind dann die Nulliterationen. Sieht man, dass man ziemlich konsistent nach dem ersten Schritt schon bei einem relativen Fehler von 0,5 ist. Man kann es auch auf allen Zahlen ausprobieren, da wir vorher im Algorithmus gerechnet haben, egal, was die Zahlen man einsetzt, man wird immer nah genug dran sein, dass im nächsten Nulliterationen man immer so bei dieser relativen Genauigkeit herauskommt. Und letztendlich, man sieht es auch im Code, diese Genauigkeit von 0,0047 oder 0,003 eigentlich gar nicht mehr fürs Spiel erforderlich war und schon nach der ersten Interaktion gesagt würde einmal Nulliterationen reicht, ein Schritt ist genug. Nur mal als Ergänzung, hätte er die andere Interaktion benutzt, wäre es nochmal deutlich besser gewesen, aber im Endeffekt hätte er eine Division drin gehabt und im Grunde genommen zwar dreimal so gute Werte gehabt, was ein Vielfaches besser ist, als das an dieser Zahl rumfummelt, aber im Grunde genommen ja schon vorher schon mit dem schlechteren Verfahren eine Genauigkeit gehabt, die ihm gereicht hat, ohne dass er eine Division gebraucht hat. Deswegen ist er da natürlich geblieben. Ja, wenn man sich dann den Algorithmus nochmal im Detail anguckt, was passiert da jetzt genau? Wir haben einmal sozusagen ja natürlich den Funktionsrumf, dann kommt hier die Zahl 1,5, die einmal definiert wird. Die braucht er genau für die Nitten-Lüten- Interaktion nachher, weil dort drei halbe drin vorkommt. Natürlich sollte der Compiler das wegoptimieren, dass das nicht immer wieder neu gesetzt werden muss. Dann brauche ich hier tatsächlich die Hälfte von X in Float gerechnet. Auch das kommt nachher in meinem Newton Algorithmus einmal drin vor. Das wird also einmal vorne ran geschoben. Ist auch ganz gut, dass in der Reihenfolgezimmer haben, auch später für Prozessoren, die womöglich das auch per Pipeline schon parallel rechnen können, weil die Zahl brauche ich erst später. Und dann kommt dieser böse Teil des Type-Punning, wo ich meine Zahl über einem Pointer tatsächlich hier kopiert. Man könnte es auch direkt umsortieren, einfach als ein Long-Interpretiere, als meine 32-Bit-Zahl, um dann meine Magie zu machen, im Logrythmus zu rechnen. Hier haben wir genau die Rechnung, die wir aus dem ersten Teil des Vortrags gesehen haben. Ich habe hier die Negation und meine Division durch 2 für 1 durch Wurzel X. Und die Zahl hier vorne entsteht schlicht durch den Bayes, den ich drauf addieren muss und aus den Klammern rausgezogen habe. Danach wandle ich sie wieder zurück, mache mein Exponentieren sozusagen und füge hinten dran einmal einen Schritt Newton, den wir hier gesehen haben, diese einmalige Newton-Iteration. Die zweite ist schlicht auskommentiert, weil sie am Ende nichts mehr gebracht hat fürs Spiel. Aber wenn jemand genauere Ergebnisse haben will, kann die natürlich auch gerne nochmal hinten dran schieben. Ja, was bedeutet das für uns nun heute? Also mal vorne weg. Bitte kein Type-Punning machen. Das ist kein gutes Programmieren. Es sei denn, man weiß wirklich ganz genau, was man da tut. Beispielsweise ist es eine Anwendung, man will ganz exakte Werte in ein Double- oder Float reinschreiben. Dann funktioniert das ziemlich gut, weil ich das nächste Dezimal besser schreiben kann. Aber außerdem und vielleicht diesen einen Algorithmus, den man aber so auch nicht mehr braucht, sollte man das nicht tun. Trotzdem gibt es viele Dinge, die davon für uns heute auch ganz, ganz wichtig sind. Eine Sache ist beispielsweise IEEE 754. Wie speichert der Computer die Zahlen? Ist heute aktueller denn je? Denn wenn ich massiv parallel arbeite und ich möchte meine Programme immer schneller machen, dann sind wir inzwischen an der Grenze angekommen, dass wir einfach die CPUs nicht mehr schneller machen können. Wir können nur noch mehr parallelisieren und um die Daten noch mehr zu parallelisieren, ist eine Methode. Ich brauche einfach weniger Daten, weniger Speicher pro Daten. Und deswegen geht ein Trend, gerade in diesem massiv Daten in sieben Anwendungen, immer kleinere Datenformate zu benutzen. Beispielsweise in den Datentyp HAV ist ein Standard-Datentyp auf der GPU, damit ich dort doppelt so viele Daten mit dem gleichen Bandbreite durchschieben kann und einfach viel mehr Datenpunkte berechnen kann, wenn auch nicht so genau. Das Problem ist nur am IEEE 754, dass speziell am HAV dieses Verhältnis zwischen Mantisse und dem Exponenten nicht flexible genug ist. Und man dort gesagt hat, okay, eigentlich interessiert mich nicht so genau diese Genauigkeit, sondern ich möchte eigentlich viel mehr Bandbreite haben. Und deswegen wurde 2017 zum Beispiel von Google Beafloat, die wurden die Beafloat-Typen herausgebracht, die genau an der Stelle herumformeln und sagen, ich mache den Bereich der Exponenten größer und ich schmeiß diese ganzen Sonderregeln weg für Infen dann und sowieso, die brauche ich eh so selten, weil ich nehme mal vorhanden, dass man Algorithmus nicht solche Zahlen rauswirft. Auf die andere Weise kriege ich mehr Daten, genauere Daten dort rein und dafür muss man sich genau damit beschäftigen, wie sieht das aus? Weil wenn ich dafür jetzt Algorithmus entwicke, in Hardware und in Software brauche ich natürlich auch für diese Darstellung genauso, wenn ich die natürlich optimieren möchte. Ja, ein anderer Ansatz, den ich in Algorithmus einfach wunderbar finde, ist, hört sich jetzt ein bisschen trivial an Logarithmen-Gesetze, sind wichtig fürs Rechnen. Na ja, mit Logarithmen zu rechnen, ja auch, im Grunde genommen ist es natürlich auch, wenn ich in Fließkammerzahlen rechne, auch das Logarithmen rechne immer mit dabei. Aber tatsächlich ist es dieses Prinzip. Ich stelle etwas in einer anderen Basis und in einer anderen Form da, ist ein Standardprinzip, wie ich Probleme im Computer lösen kann. Sei es, dass ich, ja, Fourier Transformation benutze, JPEG, mp3 und alles Mögliche andere basiert dadurch, dass ich einfach meine Zeitdaten durch, ja, Spektraldaten darstelle und damit viel besser, genauer, schneller rechnen kann. Wenn ich Differenzialgleichung habe, kann ich die mittlerweile Plastgleichung angehen, Zeit rein, dafür habe ich Z-Transformation. Und es gibt immer Arten, wie ich ein Problem anders angucken kann. Und meistens dieser Blick, ich dreh es anders rum und gucke es mir anders an, die Methode, wie ich ein Problem besser lösen kann. Und sobald man die bessere Sichtweise gefunden hat, ist meistens schon klar, wie man auch da besser dran gehen kann. Und ja, eigentlich sehr viele Probleme von heute sind so gestrickt, dass sie einfach schlicht nicht linear sind, sondern sie sind kompliziert. Sie sind nicht linear. Da brauche ich andauernd solche Methoden, die mir irgendwas in der Regularität womöglich ausnutzen, um besser dran zu kommen. Entweder ich versuche zu raten, ich mache Route Force, ich versuche es immer weiter einzuschrachten, wenn ich zumindest eine kontinuierliche Abhängigkeit habe. Wenn ich zumindest die Funktion ausrechnen kann und ich keine Ableitung habe, kann ich immer noch Sekanten ausrechnen anstatt Targenten. Wenn ich eine Ableitung habe, dann kann ich Nütenverfahren verwenden oder man kann sogar dieses Nütenverfahren noch um höhere Ordnung erweitern, indem ich polynome höhere Ordnung benutze, statt gerade um dort zu schnellerer Konvergenz zu kommen. Aber in all diesen Fällen ist es total wichtig, dass man schon eine Idee hat, wo sollte die Lösung eigentlich sein? Weil die Lösungsräume meistens so immens groß sind, dass wenn ich nicht vorher schon mal so ein Tipp habe, da ungefähr soll es liegen, man sich einfach dumm und demlich sucht, um dort zu lösen zu kommen. Und deswegen funktionieren halt viele Verfahren mit Heuristiken und Nährung so unwahrscheinlich gut, wie auch hier in diesem Fall, über diesen Trick mit dem IEE und dem Logarithmus, habe ich erst mal eine erste Schätzung, kann ich danach ein mächtiges Verfahren nicht daher schieben. Genauso funktioniert das Routing viel besser, wenn ich eine Heuristik habe oder auch hochdimensionale Probleme, wenn ich da die höheren Ordnung halt approximieren kann, dann kann ich auf eine viel schnelle Rat und Weise zu lösen kommen. Und dementsprechend ist eigentlich der R-SQRT Algorithmus nichts anderes als eine Kombination von einem ersten Schätzen Pima-Daumen verbunden mit einer genauen Iteration erst mal abschätzen, dann genau machen, dass es einfach die Methode, wie hier zum Ziel gefunden wird und die, der Algorithmus einfach wunderbar darstellt und das ganz wunderbar auf sehr grundlegende Technologien. Ja, alles, was ich hier erzählt habe, haben wahrscheinlich ganz viele vorher auch erzählt und wer das noch ein bisschen weiter erkunden möchte, es gibt wahnsinnig viele Literatur dazu. Das Ganze hat schon mit Lomond damals angefangen 2003, bevor das R-SQRT offen war. Da hat er schon über diese mathematischen Eigenschaften gesprochen. Dann kam Eberliens Self und Robertson, die dort über die ganzen Hintergunde und speziell sich auch mit der Konstante näher beschäftigt haben. Also, Robertson ist diese Bachelorarbeit gewesen. Und letztlich gab es auch in Heise vor einiger Zeit ein Artikel von Herrn Zotter. Zahlen, bitte. Merkwürdige Mathematik im Ego-Shooter. Und es wird auch noch weiter geforscht. Dort haben wir Weizig aus, ich glaube aus der Ukraine, der sich damit beschäftigt hat, wie kann ich den Nüten Algorithmus speziell die Konstanten dort noch ein bisschen anpassen, um dort noch zu besseren Ergebnissen zu kommen. Ja, ich hoffe, dass einen kleinen Einblick geben konnte, in den eigentlich ziemlich coolen Algorithmus und was dafür grundlegende Konzepte drin sind. Und ich freue mich, wenn es Fragen dazu gilt. Tests, Tests. Ja, vielen Dank, die Sebastian. Wir haben noch Zeit für Fragen. Jan, das ist wunderbar. Zuerst, ich komme für die Reichweite. Vielen Dank für den Vortrag. Funktioniert der Algorithmus auch für negative Zahlen oder geht da das mit dem Schiften von dem i kaputt? Ja, negative Zahlen. Da kommen wir gleich in die komplexen Zahlen rein, weil wir wollen ja die Wurzel aus der Zahl berechnen. Wenn wir die negativ machen, dann geht es natürlich schief. Ja, und ansonsten wird der Algorithmus natürlich an der Stelle schief gehen. Wenn ich das Vorzeichen nicht berücksichtige, würde mir das als einfach die Zahl verfälschen. Aber da will sowieso die Wurzel ausrechnen wollen, spielt das hier keine Rolle. Danke. Dann hier. Ja, sehr, sehr coole Vortrag. Meine Frage wirst wahrscheinlich nicht beantworten können. Deswegen ist eigentlich eine versteckte Aufforderung an Leute, die noch basteln wollen hier im Wochenende. Was passiert denn, wenn man diese letzte Zeile, die Vorletze, die noch kommentiert ist, die auch weglässt, also nur den ersten Schritt macht und dann quägt 3 spielt? Sieht das anders aus? Und gibt es da vielleicht ein cooles Video so mit mit vorher und nachher der gleichen Playthrough nebeneinander? Und dann könnte man vergleichen, was diese letzte Schritt eigentlich am Spiel ausmacht. Also, falls du das Video gerade nicht zu viel da hast, vielleicht hat jemand Bock, dass sie ja zu bauen übers Wochenende. Ich meine, der Code ist da. Wäre vielleicht lustig. Okay, danke, Jochim. Und falls jemand die Haskell-Implimitation interessiert, wird uns wahrscheinlich auch noch direkt dazu helfen können. Nein, aber natürlich kann sich antworten, hast du das Video nicht hier? Habt noch überlegt, ob ich auch noch was aus Quake reinpuzzle. Aber tut mir leid, kein Screenshot. Ja, super Vortrag. Und die Idee finde ich halt auch total gut. Also, bitte, wer will? Ich habe eigentlich nur zwei Bemerkungen. Das eine ist halt so aus dem real life. Wir haben in irgendeinem Projekt mal ein Bild verarbeitende Algorithmen gemacht und irgendwann kam der Kunde, ja, ist so langsam, war halt mit Fließkommazahlen und nach längerer Zeit hat dann irgendein schlauer Kollege herausgefunden, es handelt sich um denormalisierte Zahlen. Zur Erinnerung, das waren die, wo die Null in der Mantisse ist, weil der Exponent nicht mehr ausreicht. Und da haben wir dann durch Änderung der Algorithmik dafür gesorgt, dass es halt nicht mehr denormalisierte Fließkommazahlen sind. Und da war der Kunde auch wieder zufrieden. Also mal darüber lesen. Und wie ich dein Javascript gesehen habe, ist mir, muss ich doch etwas schmunzeln, weil durch diese Art der Konvertierung des Type-Punning verbrauchst natürlich so viele CPU-Zyklen, dass alles zu spät ist. Danke. Ich habe, ehrlich gesagt, auch nicht noch mal ein Benchmark gemacht. Aber es gibt im Netz Benchmarks, die tatsächlich behaupten, dass das immer noch schneller wäre, als die Wurzel direkt zu rechnen. Ja, der Beitrag ist auch ziemlich interessant. Ganz oft kommt es vor, dass man halt, wenn man weiß, wohnt und rechnet, auch halt natürlich diese Fließkommazahlen durch Fixkommazale setzen kann. Und dann auch sehr schnell vorwärtskommt. Aber dann schränkt man sich im Zahlenbereich stark ein. Ja, danke auch von mir. Weißt du, wie das aussieht in heutigen Standardbibliotheken? Also wie wird das da ausgerechnet? Genauso? Oder hat sich da jetzt ein bisschen was getan? Also ich meine, auch wenn es schneller geht, kann man ja auch halt die zweite oder dritte Interaktion ausrechnen. Früher wurde es gemacht meines Wissens. Inzwischen wird dort sehr viel mit Tabellen gearbeitet. Also vor ganz langer Zeit hatte ich auch mal das Vergnügen auch in gewissen Standardbibliotheken mal mitarbeiten zu können. Auch hier aus Karlsruhe. Da haben sie tatsächlich an Chips gebaut, um eine alternative Lösung, also statt I2B 754 umzusetzen und zwar das Ganze verifiziert zu machen. Aber ich kann es jetzt nicht sagen, wie tatsächlich momentan die umgesetzt werden. Da, aber das Ganze in Hardware gemacht wird, sind wahrscheinlich sehr, sehr viele Tabellen dahinter und verschiedene andere Ricks, die benutzt werden. Tatsächlich gibt es aber auch ein Paper, habe ich gefunden, der sich damit beschäftigt hat. Wie kann ich genau diesen Algorithmus auf dem FPGA mit Gartan umsetzen, was es dabei noch zu betrachten, einfach um zu gucken, ob diese Implementation dann schneller ist als manch anderes mag sein. Aber kann ich leider nicht mehr beantworten. Daher drüben Sie ich noch eine Frage. Ich habe nur noch eine kurze Bemerkung, und zwar in C++. Ist dieses Type-Panning und Defiant-Behaviour. Und wer sowas machen will, der soll bitte Mem-Copy benutzen, um das zu vermeiden. Ja, es ist nicht nur an die Vereint-Bitte nicht machen. Auch wenn ich es dir gezeigt habe, ich mache das hier nur aus. Historischem Interesse zeige ich dir das Type-Panning. Und wie gesagt, die Anwendung ist eigentlich eher darauf beschränkt, wie man zum Beispiel ein Double-In-Flow mit ganz bestimmten spezifischen Werten initialisieren, um damit testen zu können. Da macht Sinn, aber um schnell zu sein, sorry, inzwischen sind die, haben wir die ganzen Algorithmen in Hardware und dementsprechend sind die halt nach einem wenigen Tagzyklen fertig. Da lohnt das nicht mehr. Aber auf emulierten Maschinen kann man sich das noch überlegen. Aber trotzdem, bitte, aus Software-Engineering-Aspekten keinen Type-Panning machen. Das macht uns alles kaputt. Sehr weise Worte. Vielen Dank, die Sebastian. Wenn es keine Fragen mehr gibt, bitte ich noch mal einem großen Applaus für diesen Vortrag.