Bild - www.freepik.com
Vor einigen Jahren habe ich viel über Gleitkomma-Mathematik nachgedacht und geschrieben. Es war sehr interessant, und in den Prozess der Forschung, habe ich viel gelernt, aber manchmal habe ich lange Zeit nicht nutzen alle inPraxis diese Fähigkeiten erhielt schwere Arbeit. Daher freue ich mich jedes Mal sehr, wenn ich an einem Fehler arbeiten muss, der verschiedene Fachkenntnisse erfordert. In diesem Artikel werde ich drei Geschichten über Gleitkomma-Fehler erzählen, die ich in Chromium gelernt habe.
Teil 1: unrealistische Erwartungen
Der Fehler wurde "JSON analysiert 64-Bit-Ganzzahlen nicht korrekt" genannt. Es sieht auf den ersten Blick nicht nach einem Gleitkomma- oder Browserproblem aus, wurde jedoch auf crbug.com veröffentlicht, sodass ich gebeten wurde, einen Blick darauf zu werfen. Am einfachsten können Sie es neu erstellen, indem Sie die Chrome-Entwicklertools (F12 oder Strg + Umschalt + I) öffnen und den folgenden Code in die Entwicklerkonsole einfügen:
json = JSON.parse(‘{“x”: 2940078943461317278}’); alert(json[‘x’]);
Das Einfügen von unbekanntem Code in das Konsolenfenster ist eine großartige Möglichkeit, gehackt zu werden, aber der Code war so einfach, dass ich herausfinden konnte, dass er nicht bösartig war. Im Fehlerbericht gab der Autor freundlicherweise seine Erwartungen und tatsächlichen Ergebnisse an:
Was ist das erwartete Verhalten? Ein ganzzahliger Wert von 2940078943461317278 sollte zurückgegeben werden.
Was ist der Fehler? Stattdessen wird eine Ganzzahl 2940078943461317000 zurückgegeben.
Der "Fehler" wurde unter Linux gefunden und ich arbeite an Chrome für Windows, aber dieses Verhalten ist plattformübergreifend und ich hatte Kenntnisse über Gleitkommazahlen, also habe ich es untersucht.
Dieses Verhalten von Ganzzahlen ist möglicherweise ein Gleitkomma-Fehler, da JavaScript wirklich keinen Ganzzahltyp enthält. Und aus dem gleichen Grund ist dies eigentlich kein Fehler.
Die eingegebene Zahl ist ziemlich groß und entspricht ungefähr 2,9e18. Und das ist das Problem. Da JavaScript keinen ganzzahligen Typ hat, wird für Zahlen die Gleitkomma-Doppelgenauigkeit IEEE-754 verwendet . Dieses binäre Gleitkommaformat hat ein Vorzeichenbit, einen 11-Bit-Exponenten und eine 53-Bit-Mantisse (ja, es sind 65 Bit, ein Bit ist durch Magie verborgen). Dieser Doppeltyp kann so gut Ganzzahlen speichern, dass viele JavaScript-Programmierer nie bemerkt haben, dass es keinen Ganzzahltyp gibt. Sehr viele zerstören jedoch diese Illusion.
Die JavaScript-Nummer kann jeden ganzzahligen Wert bis zu 2 ^ 53 präzise speichern. Danach können alle geraden Zahlen bis zu 2 ^ 54 gespeichert werden. Und dann kann es alle Vielfachen von vier Zahlen bis zu 2 ^ 55 speichern und so weiter.
Die Problemnummer wird in der Exponentialschreibweise der Basis 2 ausgedrückt, die ungefähr 1,275 * 2 ^ 61 beträgt. In diesem Intervall kann nur eine sehr kleine Anzahl ganzer Zahlen ausgedrückt werden - der Abstand zwischen den Zahlen beträgt 512. Hier sind die drei entsprechenden Zahlen:
- 2 940 078 943 461 317 278 ist die Nummer, die der Autor des Fehlerberichts behalten wollte
- 2 940 078 943 461 317 120 - doppelt so nahe an dieser Zahl (weniger als sie)
- 2 940 078 943 461 317 632 - die nächstgelegene Zahl doppelt (größer als sie)
Die Zahl, die wir benötigen, befindet sich im Intervall zwischen diesen beiden Doubles, und das JSON-Modul (z. B. JavaScript selbst oder eine andere korrekt implementierte Funktion zum Konvertieren von Text in Double) hat sein Bestes gegeben und das nächste Double zurückgegeben. Einfach ausgedrückt, die Nummer, die der Autor des Berichts speichern wollte, kann nicht im integrierten numerischen JavaScript-Typ gespeichert werden .
Bisher ist alles klar: Wenn Sie an die Grenzen der Sprache stoßen, müssen Sie mehr darüber wissen, wie sie funktioniert. Aber es gibt noch ein Rätsel. Der Fehlerbericht besagt, dass tatsächlich die folgende Nummer zurückgegeben wird:
2 940 078 943 461 317 000
Die Situation ist merkwürdig, weil es sich nicht um eine eingegebene Zahl handelt, nicht um das nächste Doppel, und tatsächlich nicht einmal um eine Zahl, die als Doppel dargestellt werden kann!
Dieses Rätsel wird auch durch die JavaScript-Spezifikation erklärt. Die Spezifikation besagt, dass eine Implementierung beim Drucken einer Nummer eine ausreichende Anzahl von Ziffern ausgeben muss, um sie eindeutig zu identifizieren, und nicht mehr. Dies ist nützlich zum Drucken von Zahlen wie 0.1, die nicht genau als Doppel dargestellt werden können. Wenn JavaScript beispielsweise die Ausgabe von 0.1 als gespeicherten Wert erfordert, wird Folgendes ausgegeben:
0.1000000000000000055511151231257827021181583404541015625
Es wäre ein genaues Ergebnis , aber es würde die Leute nur verwirren, indem es nichts Nützliches hinzufügt. Spezifische Regeln finden Sie hier (siehe Zeile "ToString angewendet auf den Nummerntyp"). Ich glaube nicht, dass die Spezifikation nachgestellte Nullen erfordert , aber das tut es auf jeden Fall.
Wenn das Programm ausgeführt wird, gibt JavaScript 2.940.078.943.461.317.000 aus, weil:
- Der ursprüngliche Nummernwert ging beim Speichern als JavaScript-Nummer verloren
- Die angezeigte Nummer liegt nahe genug am gespeicherten Wert, um ihn eindeutig zu identifizieren
- Die angezeigte Nummer ist die einfachste Nummer, die den gespeicherten Wert eindeutig identifiziert
Alles funktioniert wie es sollte, dies ist kein Fehler, das Problem wird als WontFix ("nicht behebbar") geschlossen. Den ursprünglichen Fehler finden Sie hier .
Teil 2: schlechtes Epsilon
Dieses Mal habe ich den Fehler behoben, zuerst in Chromium und dann in googletest, um Verwirrung für zukünftige Entwicklergenerationen zu vermeiden.

Dieser Fehler war ein nicht deterministischer Testfehler , der plötzlich auftrat. Wir hassen diese Fuzzy-Testfehler. Sie sind besonders verwirrend, wenn sie in einem Test beginnen, der sich seit Jahren nicht geändert hat. Einige Wochen später wurde ich zur Untersuchung gebracht. Die Fehlermeldungen (geringfügig geändert für Zeilenlängen) begannen wie folgt:
Der Unterschied zwischen erwarteten Mikrosekunden und konvertierten Mikrosekunden beträgt 512, was 1,0 überschreitet. [Der Unterschied zwischen erwarteten Mikrosekunden und konvertierten Mikrosekunden beträgt 512, was 1,0 überschreitet.]
Ja, das hört sich schlecht an. Dies ist eine googletest Fehlermeldung, die besagt, dass zwei Gleitkommawerte, die nicht mehr als 1,0 voneinander entfernt sein sollten, tatsächlich 512 voneinander entfernt sind. Der
erste Beweis war der Unterschied zwischen Gleitkommazahlen. Es schien sehr verdächtig, dass die beiden Zahlen durch genau 2 ^ 9 getrennt sind. Zufall? Ich glaube nicht. Der Rest des Beitrags, in dem die beiden verglichenen Werte angegeben waren, überzeugte mich noch mehr vom Grund:
Expected_Microseconds ergibt 4.2934311416234112e + 18,
konvertierte_Microseconds ergibt 4.2934311416234107e + 18
Wenn Sie lange genug mit IEEE 754 gekämpft haben , werden Sie sofort verstehen, was passiert.
Sie haben den ersten Teil gelesen, damit Sie sich aufgrund der gleichen Zahlen déjà vu fühlen können. Dies ist jedoch ein reiner Zufall - ich verwende nur die Zahlen, auf die ich gestoßen bin. Diesmal wurden sie im Exponentialformat angezeigt, was den Artikel etwas abwechslungsreicher macht.
Das Hauptproblem ist eine Variation des Problems aus dem ersten Teil: Gleitkommazahlen in Computern unterscheiden sich von den reellen Zahlen, die von Mathematikern verwendet werden. Sie werden mit zunehmender Genauigkeit ungenauer, und alle Doppelwerte waren notwendigerweise Vielfache von 512 im Bereich der fehlerhaften Zahlen. Doppel hat eine Genauigkeit von 53 Bit, und diese Zahlen waren viel größer als 2 ^ 53, so dass eine signifikante Verringerung der Genauigkeit unvermeidlich war. Und jetzt können wir das Problem verstehen.
Der Test berechnete den gleichen Wert auf zwei verschiedene Arten. Dann überprüfte er, ob die Ergebnisse nahe beieinander lagen, wobei „Nähe“ einen Unterschied innerhalb von 1,0 bedeutete. Die Berechnungsmethoden ergaben sehr ähnliche Antworten, so dass die Ergebnisse in den meisten Fällen mit doppelter Genauigkeit auf den gleichen Wert gerundet wurden. Allerdings von Zeit zu ZeitDie richtige Antwort liegt in der Nähe der Beugung, und eine Berechnung rundet eine Richtung und die andere eine andere ab.
Als Ergebnis wurden insbesondere die folgenden Zahlen verglichen:
- 4293431141623410688
- 4293431141623411200
Ohne Exponenten fällt auf, dass sie durch genau 512 getrennt sind. Die beiden unendlich genauen Ergebnisse der Testfunktionen unterschieden sich immer um weniger als 1,0, dh wenn es sich um Werte wie 429 ... 10653,5 und 429 ... 10654,3 handelte, wurden beide auf 429 ... 10688 gerundet. Das Problem trat auf, wenn unendlich genaue Ergebnisse nahe an einem Wert wie 4293431141623410944 lagen. Dieser Wert liegt genau auf halbem Weg zwischen zwei Doppelwerten. Wenn eine Funktion 429 ... 10943.9 und die andere 429 ... 10944.1 generiert, wurden diese Ergebnisse, geteilt durch einen Wert von nur 0,2, in verschiedene Richtungen gerundet und in einem Abstand von 512 gelandet!
Dies ist die Art der Beugung oder Schrittfunktion. Sie können zwei Ergebnisse erhalten, die beliebig nahe beieinander liegen, sich jedoch auf gegenüberliegenden Seiten der Beugung befinden - genau in der Mitte zwischen den beiden Punkten - und daher in verschiedene Richtungen gerundet sind. Es wird oft empfohlen, den Rundungsmodus zu ändern, aber das hilft nicht - es verschiebt nur den Wendepunkt.
Es ist, als hätte man gegen Mitternacht ein Baby - eine winzige Abweichung kann das Datum (möglicherweise ein Jahr, ein Jahrhundert oder ein Jahrtausend) der Registrierung der Veranstaltung dauerhaft ändern.
Vielleicht war meine Festschreibungsnotiz zu dramatisch, aber unverkennbar. Ich fühlte mich wie ein einzigartiger Spezialist, der in der Lage ist, mit dieser Situation umzugehen:
Commit 6c2427457b0c5ebaefa5c1a6003117ca8126e7bc
Autor: Bruce Dawson
Datum: Fri Dec 08 21:58:50 2017
Fix Epsilon-Berechnung für große Doppelvergleiche
Mein ganzes Leben hat zu diesem Bugfix geführt. [Mein ganzes Leben hat mich dazu gebracht, diesen Fehler zu beheben.]
In der Tat schaffe ich es selten, eine Änderung in Chromium mit einem Commit-Hinweis vorzunehmen , der ziemlich vernünftigerweise mit zwei (2!) Meiner Posts verknüpft ist .
In diesem Fall bestand die Lösung darin, die Differenz zwischen zwei benachbarten Doppelwerten mit der Größe der berechneten Werte zu berechnen. Dies wurde mit der selten verwendeten nextafter- Funktion durchgeführt . Ungefähr so:
epsilon = nextafter(expected, INFINITY) – expected;
if (epsilon < 1.0)
epsilon = 1.0;
Die nextafter- Funktion findet das nächste Double (in diesem Fall in der Unendlichkeitsrichtung), und die Subtraktion (was genau erfolgt und dies sehr praktisch ist) findet dann die Differenz zwischen den Doubles bei ihrem Wert. Der getestete Algorithmus ergab einen Fehler von 1,0, daher sollte epsilon nicht größer als dieser Wert sein. Diese Berechnung von epsilon macht es sehr einfach zu überprüfen, ob die Werte weniger als 1,0 voneinander entfernt sind oder benachbarte Doppelwerte.
Ich habe den Grund, warum der Test plötzlich fehlschlug, nicht untersucht, aber ich vermute, dass es eine Timerfrequenz oder eine Änderung des Timer-Startpunkts ist, die dazu geführt hat, dass die Zahlen größer wurden.
. QueryPerformanceCounter (QPC), <int64>::max(), 2^63-1. , . , , QPC 2 148 . , QPC, , , , , 3 . QPC 2^63-1 , .
, , QueryPerformanceCounter.
googletest

Ich war verärgert darüber, dass das Verständnis des Problems esoterische Kenntnisse der Gleitkomma-Besonderheiten erforderte, deshalb wollte ich den Googletest beheben . Mein erster Versuch endete schlecht.
Ich habe ursprünglich versucht, googletest zu beheben, indem EXPECT_NEAR beim Übertragen eines unbedeutend kleinen Epsilons fehlschlug. Es scheint jedoch, dass viele Tests innerhalb von Google und wahrscheinlich viele weitere außerhalb von Google EXPECT_NEAR fälschlicherweise für doppelte Werte verwenden. Sie übergeben einen Epsilon-Wert, der zu klein ist, um nützlich zu sein, aber die Zahlen, die sie vergleichen, sind dieselben, sodass der Test erfolgreich ist. Ich habe ein Dutzend Punkte bei der Verwendung von EXPECT_NEAR behoben, ohne das Problem zu lösen, und habe aufgegeben.
Erst als ich diesen Beitrag schrieb (fast drei Jahre nachdem der Fehler aufgetreten war!), Wurde mir klar, wie sicher und einfach es war, Googletest zu reparieren. Wenn der Code EXPECT_NEAR mit zu wenig Epsilon verwendet und der Test erfolgreich ist (dh die Werte sind tatsächlich gleich), ist dies kein Problem. Dies wird zu einem Problem nur dann , wenn der Test nicht bestanden , so dass ich für zu kleines epsilon Werte nur musste schauen nur im Fall eines Ausfalls und zeige eine informative Nachricht zur gleichen Zeit.
Ich habe diese Änderung vorgenommen und jetzt sieht die Fehlermeldung für diesen Absturz 2017 folgendermaßen aus:
expected_microseconds converted_microseconds 512,
expected_microseconds 4.2934311416234112e+18,
converted_microseconds evaluates to 4.2934311416234107e+18.
abs_error 1.0, double , 512; EXPECT_NEAR EXPECT_EQUAL. EXPECT_DOUBLE_EQ.
Beachten Sie, dass EXPECT_DOUBLE_EQ nicht auf Gleichheit prüft, sondern prüft, ob Doppel gleich vier Einheiten in der letzten Ziffer sind (Einheiten an letzter Stelle, ULP). Weitere Informationen zu diesem Konzept finden Sie in meinem Beitrag Vergleichen von Gleitkommazahlen .
Ich hoffe, dass die meisten Softwareentwickler diese neue Fehlermeldung sehen und den richtigen Weg einschlagen, und ich glaube, dass das Beheben des Googletests letztendlich wichtiger ist als das Beheben des Chromium-Tests.
Teil 3: wenn x + y = x (y! = 0)
Dies ist eine weitere Variante von Präzisionsproblemen bei der Annäherung an Grenzen: Vielleicht finde ich immer wieder denselben Gleitkomma-Fehler?
In diesem Teil werde ich auch die Debugging-Techniken beschreiben, die Sie anwenden können, wenn Sie den Chromium-Quellcode oder die Ursache des Absturzes untersuchen möchten.

Als ich auf dieses Problem stieß, veröffentlichte ich einen Fehlerbericht mit dem Titel " Absturz mit OOM-Fehler (Out of Memory) in Chrome: // Ablaufverfolgung beim Vergrößern "; Es klingt nicht nach einem Gleitkomma-Fehler.
Wie üblich suchte ich nicht selbst nach Problemen, sondern studierte nur Chrome: // Tracing und versuchte, einige der Ereignisse zu verstehen. Plötzlich erschien ein trauriger Tab - es gab einen Fehler.
Sie können die neuesten Abstürze für Chrome unter chrome: // abstürzen anzeigen und herunterladen, aber ich wollte den Absturzspeicherauszug in den Debugger laden, also habe ich nachgesehen, wo sie lokal gespeichert sind:
% localappdata% \ Google \ Chrome \ Benutzerdaten \ Crashpad \ Berichte
Ich habe den neuesten Crash-Dump auf windbg hochgeladen (Visual Studio wird es auch tun) und dann weiter nachgeforscht. Da ich die Chrome- und Microsoft-Symbolserver konfiguriert und den Quellserver aktiviert hatte, lud der Debugger automatisch den PDB (Debug-Informationen) und die erforderlichen Quelldateien herunter. Beachten Sie, dass dieses Schema für alle verfügbar ist. Sie müssen kein Google-Mitarbeiter oder Chromium-Entwickler sein, damit diese Magie funktioniert. Anweisungen zum Einrichten des Chrome / Chromium-Debuggens finden Sie hier . Der automatische Download des Quellcodes erfordert die Installation von Python.
Die Absturzanalyse ergab, dass der Fehler aufgrund von Speichermangel auf die Tatsache zurückzuführen ist, dass die v8-Funktion (JavaScript-Engine) NewFixedDoubleArray funktioniertversucht, ein Array mit 75.209.227 Elementen zuzuweisen, und die in diesem Kontext maximal zulässige Größe beträgt 67.108.863 (0x3FFFFFF in hex).
Das Schöne an den Störungen, die ich selbst verursacht habe, ist, dass Sie versuchen können, sie mit einer genaueren Überwachung wiederherzustellen. Experimente zeigten, dass der Speicher beim Zoomen stabil blieb, bis ich einen kritischen Punkt erreichte. Danach stieg die Speichernutzung plötzlich in die Höhe und die Registerkarte stürzte ab, selbst wenn ich nichts tat.
Das Problem hierbei war, dass ich den Aufrufstapel für diesen Fehler problemlos anzeigen konnte, jedoch nur im C ++ - Teil des Chrome-Codes. Anscheinend trat der Fehler jedoch im chrome: // Tracing JavaScript-Code auf. Ich habe versucht, es mit einem kanarischen Build von Chrome (täglich) unter dem Debugger zu testen, und habe die folgende merkwürdige Nachricht erhalten:
==== JS-Stack-Trace ======================================
Leider gab es hinter dieser interessanten Linie keine Stapelspur. Nachdem ich ein bisschen in der Wildnis von Git gewandert war , fand ich heraus, dass die Möglichkeit, JS-Aufrufstapel über OOM auszugeben, 2015 hinzugefügt und dann im Dezember 2019 entfernt wurde .
Ich recherchieren diesen Fehler am Anfang Januar 2020 (die guten alten Zeiten erinnert , wenn alles unschuldig war und einfacher?), Und es bedeutete , dass der Stack - Trace Code OOM ist aus dem täglichen Build entfernt wurde, aber immer noch auf einem stabilen Montag geblieben ...
Deshalb Mein nächster Schritt war der Versuch, den Fehler in der stabilen Version von Chrome neu zu erstellen. Dies gab mir die folgenden Ergebnisse (ich habe sie aus Gründen der Klarheit ein wenig bearbeitet):
0: ExitFrame [pc: 00007FFDCD887FBD]
1: drawGrid_ [000016011D504859] [chrome: //tracing/tracing.js: ~ 4750]
2: draw [000016011D504821] [chrome: //tracing/tracing.js: 4750]

Kurz gesagt, der OOM-Absturz wurde durch drawGrid_ verursacht , das ich (mithilfe der Chromium-Code- Suchseite) in x_axis_track.html gefunden habe. Nachdem ich diese Datei ein wenig optimiert hatte, beschränkte ich sie auf den Aufruf von updateMajorMarkData . Diese Funktion enthält eine Schleife, die die Funktion majorMarkWorldPositions_.push aufruft, die den Grund für das Problem darstellt.
Es ist erwähnenswert, dass ich, obwohl ich einen Browser entwickle, der schlechteste JavaScript-Programmierer der Welt bleibe. Kenntnisse in der Programmierung von C ++ - Systemen geben mir nicht die Magie des "Frontends". Das Hacken von JavaScript, um diesen Fehler zu verstehen, war für mich ein ziemlich schmerzhafter Prozess.
Die Schleife (die hier zu sehen ist ) sah ungefähr so aus:
for (let curX = firstMajorMark;
curX < viewRWorld;
curX += majorMarkDistanceWorld) {
this.majorMarkWorldPositions_.push(
Math.floor(MAJOR_MARK_ROUNDING_FACTOR * curX) /
MAJOR_MARK_ROUNDING_FACTOR);
}
Ich habe vor der Schleife Debug-Ausgabeanweisungen hinzugefügt und die unten gezeigten Daten erhalten. Als ich das Bild vergrößerte, sahen die Zahlen, die kritisch waren, aber nicht ausreichten, um einen Absturz zu verursachen, folgendermaßen aus:
firstMajorMark: 885.0999999642371
majorMarkDistanceWorld: 1e-13
Dann habe ich hineingezoomt, um einen Absturz zu verursachen, und ich habe Zahlen wie diese erhalten:
firstMajorMark: 885.0999999642371
majorMarkDistanceWorld: 5e-14
885 geteilt durch 5e-14 ist 1,8e16, und die Genauigkeit einer Gleitkommazahl mit doppelter Genauigkeit beträgt 2 ^ 53, was 9,0e15 entspricht. Daher tritt ein Fehler auf, wenn die majorMarkDistanceWorld (Abstand zwischen Gitterpunkten) im Verhältnis zu firstMajorMark (der Position der ersten großen Gittermarkierung) so klein ist, dass das Hinzufügen in einer Schleife ... nichts bewirkt. Das heißt, wenn wir einer großen Zahl eine kleine Zahl hinzufügen, kann die große Zahl (in der Standard- / vernünftigen Rundung auf den nächsten Modus) gleich dem gleichen Wert bleiben, wenn die kleine Zahl "zu klein" ist.
Aus diesem Grund wird die Schleife unbegrenzt ausgeführt und der Push-Befehl wird ausgeführt, bis das Array auf seine Größe beschränkt ist. Wenn es keine Größenbeschränkungen gäbe, würde der Push-Befehl so lange ausgeführt, bis der gesamte Computer nicht mehr über genügend Speicher verfügt. Also Hurra, Problem gelöst?
Das Update erwies sich als ziemlich einfach - zeigen Sie keine Rasterbeschriftungen an, wenn wir nicht können:
if (firstMajorMark / majorMarkDistanceWorld > 1e15) return;

Wie so oft bei den von mir vorgenommenen Änderungen bestand mein Bugfix aus einer Codezeile und einem sechszeiligen Kommentar. Ich bin nur überrascht, dass es keine fünfzig Zeilen umfassenden iambischen Pentameter-Festschreibungsnotizen, Notationsnotationen und Blogposts gab. Warten Sie eine Minute ...
Leider werden JavaScript-Stapelrahmen bei OOM-Abstürzen immer noch nicht angezeigt, da zum Schreiben von Anrufstapeln Speicher benötigt wird, was bedeutet, dass dies zu diesem Zeitpunkt nicht sicher ist. Ich verstehe nicht ganz, wie ich diesen Fehler heute untersuchen würde, wenn die OOM-Stack-Frames vollständig entfernt wurden, aber ich bin sicher, ich würde einen Weg finden.
Wenn Sie ein JavaScript-Entwickler sind, der versucht, extrem große Zahlen zu verwenden, ein Testschreiber, der versucht, den größten ganzzahligen Wert zu verwenden, oder eine Benutzeroberfläche mit unbegrenztem Zoom implementiert, ist es wichtig, sich daran zu erinnern, dass diese Grenzen durchbrochen werden können, wenn Sie sich den Grenzen der Gleitkomma-Mathematik nähern.
Werbung
Entwicklungsserver sind episch von Vdsina.
Wir verwenden extrem schnelle NVMe-Laufwerke von Intel und sparen keine Hardware - nur Markengeräte und die modernsten Lösungen auf dem Markt!
