Informationen zu verÀnderlichen Methoden eines Math-Objekts in JavaScript

Heute veröffentlichen wir eine Übersetzung eines Artikels ĂŒber mathematische Berechnungen in JavaScript, einer schriftlichen Version der PrĂ€sentation des Autors auf WaffleJS . Und diese Aussage selbst war eine Fortsetzung dieses GesprĂ€chs auf Twitter.


Mathematikunterricht

Interesse an Mathe


Alles begann mit meiner mathematischen Ausbildung, deren Erhalt ich etwas verzögerte. Als ich Informatik studierte, konnte ich mit erstklassigen Mathematiklehrern kommunizieren, aber ich habe diese Gelegenheit verpasst. Ich mochte Mathe nicht: Die untersuchten Themen waren sehr weit von der Praxis entfernt. Außerdem war ich durch ein tief theoretisches Programm der Computerausbildung bereits aus dem Gleichgewicht geraten. Ich glaubte dann, dass sie vom Leben geschieden war, und zum grĂ¶ĂŸten Teil denke ich das auch weiterhin.

Aber jetzt, ein paar Jahre nach Abschluss meines Studiums, erwachte in mir ein Durst nach Mathematik. Ich war inspiriert, welchen Einfluss selbst eine kleine Anwendung mathematischer Kenntnisse auf meine Arbeit und mein Hobby haben kann. Aber ich hatte keinen klaren Studienplan.

2012 fand ich dann einen Weg, Mathematik zu studieren, indem ich mit der Arbeit am Simple Statistics- Projekt begann . Seitdem habe ich dieses Projekt erweitert und unterstĂŒtzt. Jetzt enthĂ€lt es Implementierungen vieler Algorithmen, und es stellte sich heraus, dass es sich um eine der „ sternenklaren “ mathematischen JavaScript-Bibliotheken handelt. Und anscheinend nutzen die Leute diese Bibliothek wirklich.

Aber ich habe 2012 angefangen zu arbeiten. Wenn wir darĂŒber sprechen, wie sich die Technologie in dieser Zeit verĂ€ndert hat, dann ist es sehr, sehr lange her. Seitdem wurden 8 LTS-Versionen von Node.js veröffentlicht . Seitdem haben sich JavaSript selbst und die Umgebungen, in denen in dieser Sprache geschriebene Programme funktionieren, stark verĂ€ndert. Im Jahr 2012 existierte die React-Bibliothek noch nicht, dann wurde das erste Commit fĂŒr das Babel-Projekt noch nicht vorgenommen.


Der Lauf der Zeit

Im Laufe der Jahre bemerkte ich, dass meine Tests beim Aktualisieren von Node.js abstĂŒrzten. Zum Beispiel könnte ich so etwas wie diesen Test haben:

t.equal(ss.gamma(11.54), 13098426.039156161);

Dieser Test funktioniert in Node.js v10 einwandfrei, bricht jedoch in Node.js v12 ab. Und hier ist es nicht einig super-kompliziert Methode , die getestet wird: die Funktion gammaimplementiert Standard - JavaScript - Funktionen mit - Math.pow, Math.sqrtund Math.sin.

Arithmetik


Ich weiß, woran Sie hier denken können: Rechnen. Auf Twitter kommt es aufgrund der Ergebnisse der Berechnung des folgenden Ausdrucks regelmĂ€ĂŸig zu heftigen Diskussionen:

0.1 + 0.2 = 0.30000000000000004

Aber wie ich bereits schrieb , verhalten sich alle gĂ€ngigen Programmiersprachen so, auch altmodische und pedantische wie Haskell. Gleitkomma-Arithmetik mag seltsam aussehen, aber sie verhalten sich gleich, ihr Verhalten ist gut dokumentiert. Wir sprechen nĂ€mlich ĂŒber den IEEE 754- Standard , dessen Anforderungen streng in Programmiersprachen implementiert sind. Das Problem liegt also nicht in der Arithmetik: Bei der Implementierung von Addition, Subtraktion, Division und Multiplikation in Sprachen kann die Programmierung als „in Stein gemeißelt“ bezeichnet werden.

Mathematisches Objekt


Mein Problem war im Standard-JavaScript-Objekt Math. Insbesondere bei allen Methoden dieses Objekts.

Techniken wie Math.sin, Math.cos, Math.exp, Math.pow, Math.tan- sind die Grundzutaten fĂŒr geometrische und andere Berechnungen. Als ich das verstand, begann ich, die Änderungen im Verhalten der grundlegenden Methoden des Objekts Mathin verschiedenen Versionen von Node.js separat zu untersuchen. Hier sind einige Beispiele.

Berechnung Math.tanh(0.1):

// Node 4
0.09966799462495590234
// Node 6
0.09966799462495581907

Berechnung Math.pow(1/3, 3):

// Node 10
0.03703703703703703498
// Node 12
0.03703703703703702804

Schlimmer noch, dieses Problem tritt nicht nur in Node.js auf. Das gleiche passiert in Browsern und in anderen Umgebungen, die JavaScript unterstĂŒtzen.

Dies fĂŒhrt uns zu folgender Frage: Was ist mathematische Berechnung?


Grafische Darstellung von Berechnungen

Trigonometrische Methoden sind leicht zu visualisieren. Wenn Sie einen einzelnen Kreis und mehrere Monate Highschool zur VerfĂŒgung haben, wissen Sie, dass Kosinus und Sinus die Koordinaten eines bestimmten Punktes am Rand des Kreises sind und dass die Graphen der Funktionen sin und cos wie Wellen aussehen. TatsĂ€chlich lernen sie in der High School, wie man diese Werte erhĂ€lt, aber die dafĂŒr verwendete Methode - die Taylor-Reihe - beruht auf einer unendlichen Reihe, und es ist fĂŒr einen Computer nicht einfach, solche Probleme zu lösen.

Folgendes können Sie von Wikipedia lernen:zum Sinusberechnungsalgorithmus: „Es gibt keinen Standardalgorithmus zur Berechnung des Sinus. IEEE 754-2008, der am weitesten verbreitete Standard fĂŒr Gleitkommaberechnungen, hat keinen Einfluss auf die Berechnung trigonometrischer Funktionen wie eines Sinus. "

Computer verwenden viele verschiedene NĂ€herungen und Algorithmen, um Berechnungen durchzufĂŒhren, wie CORDIC , alle möglichen kniffligen Tricks und Nachschlagetabellen. All diese HeterogenitĂ€t erklĂ€rt das Vorhandensein vieler Fastmath-Bibliotheken auf GitHub . Tatsache ist, dass es viele Möglichkeiten gibt, die Methode zu implementieren Math.sin. Und andere Funktionen auch. Wie Sie wissen, hat die Quake III Arena beispielsweise eine schnellere verwendetErsetzen der Standardmethode zur Berechnung der inversen Quadratwurzel, um das Rendern zu beschleunigen.

Mathematische Berechnungen sind daher das Ergebnis der Implementierung bestimmter Algorithmen. In der Praxis werden viele gÀngige Algorithmen und deren Varianten verwendet.

Die JavaScript-Spezifikation gibt, anstatt anzugeben, welcher bestimmte Algorithmus in Sprachimplementierungen verwendet werden soll, Implementierungen viel Handlungsspielraum in Bezug auf Funktionen, die in mathematischen Berechnungen verwendet werden.

Dies sagt der Standard dazu (ECMA-262, Ausgabe 10, Abschnitt 20.2.2):

"Das Verhalten der Funktionen acos, acosh, asin, asinh, atan, atanh, atan2, cbrt, cos, cosh, exp, expm1, hypot, log, log1p, log2, log10, pow, zufĂ€llig, sin, sinh, sqrt, tan und tanh es wird hier nicht vollstĂ€ndig beschrieben, mit Ausnahme der Anforderungen fĂŒr die RĂŒckgabe bestimmter Ergebnisse fĂŒr bestimmte Werte der Argumente, die bemerkenswerte GrenzfĂ€lle sind. "

Ich weiß nicht, wie die internen AktivitĂ€ten der fĂŒr den ECMA-262-Standard zustĂ€ndigen Mitglieder des Ausschusses funktionieren, aber ich glaube, dass sie den Standard so festgelegt haben, dass es keine KompatibilitĂ€tskrise in JavaScript gibt, wenn Intel oder AMD neue ultraschnelle mathematische Anweisungen herausgeben in ihren frischen Prozessoren.

Aufgrund der Tatsache, dass es viele weit verbreitete JavaScript-Interpreter gibt, aufgrund der Tatsache, dass JavaScript hĂ€ufig in Browsern verwendet wird, und zwischen Browsern gibt es immer noch so etwas wie Konkurrenz, und aufgrund der Tatsache, dass selbst beliebte JavaScript-Implementierungen unter stĂ€ndigem Druck stehen und gezwungen, sich schnell zu entwickeln und die beste Leistung zu liefern ... aufgrund all dessen haben wir das, was wir haben. Jeder, der JavaScript verwendet, wird regelmĂ€ĂŸig feststellen, dass in verschiedenen Implementierungen die Ergebnisse mathematischer Berechnungen, die mit Hilfe des Objekts durchgefĂŒhrt Mathwerden, unterschiedlich sind.

Dies ist in anderen interpretierten Sprachen nicht so wichtig, da sie normalerweise einige „kanonische“ Implementierungen haben. Dies gilt beispielsweise fĂŒr den Python-Interpreter.

Wo werden die Berechnungen durchgefĂŒhrt?


Schauen wir uns nun genauer an, wo die Berechnungen durchgefĂŒhrt werden. Bei JavaScript gibt es drei Bereiche, in denen grundlegende mathematische Berechnungen durchgefĂŒhrt werden:

  1. ZENTRALPROZESSOR.
  2. Sprachinterpreter (C ++ - und C-Code einer bestimmten JavaScript-Implementierung).
  3. JavaScript-Code, zum Beispiel Code fĂŒr spezialisierte Bibliotheken.

▍1. Zentralprozessor


Die erste Idee, die mir einfiel, als ich ĂŒber die Orte nachdachte, an denen die Berechnungen durchgefĂŒhrt wurden, war, dass die Berechnungen im Prozessor durchgefĂŒhrt werden. Ich schlug vor, dass Prozessoren, da sie arithmetische Berechnungen implementieren, auch einige komplexere Berechnungen implementieren können. Es stellte sich heraus, dass die Prozessoren Anweisungen zum AusfĂŒhren trigonometrischer und anderer Berechnungen haben, diese Anweisungen werden jedoch selten verwendet. Beispielsweise war die Implementierung der Sinusberechnung in Prozessoren mit x86-Architektur nicht sehr beliebt, da sich herausstellt, dass diese Implementierung nicht unbedingt schneller ist als Softwareimplementierungen (solche, die Prozessorarithmetikoperationen verwenden). DarĂŒber hinaus ist es nicht unbedingt genauer als Software-Implementierungen.

Intel schĂ€mte sich auch wegen der sehr starkenÜberschĂ€tzung der Genauigkeit trigonometrischer Operationen in der Dokumentation. Solche Fehler sind besonders tragisch, da ein Mikrochip im Gegensatz zu einem Programm nicht gepatcht werden kann.

▍2. Sprachdolmetscher


So funktionieren die Computersubsysteme in den meisten JavaScript-Implementierungen. Sie implementieren diese Subsysteme auf verschiedene Arten.

  • Die V8- und SpiderMonkey-Engines verwenden fdlibm- Bibliotheksports fĂŒr Berechnungen , die sich geringfĂŒgig unterscheiden. Diese ursprĂŒnglich bei Sun Microsystems geschriebene Bibliothek wurde von Generation zu Generation weitergegeben.
  • JavaScriptCore (Safari) verwendet die cmath-Bibliothek, um die meisten VorgĂ€nge auszufĂŒhren.
  • Internet Explorer verwendet sowohl cmath als auch einige in Assembler geschriebene Codeblöcke . Hier wurden sogar trigonometrische Methoden von Prozessoren verwendet - fĂŒr den Fall, dass der Browser fĂŒr Prozessoren kompiliert wurde, die Ă€hnliche Anweisungen hatten.

Aus historischen GrĂŒnden haben sich die Tools zur DurchfĂŒhrung von Berechnungen in verschiedenen JS-Engines geĂ€ndert. Daher verwendete V8 eine eigene Lösung fĂŒr Berechnungen, dann wurde der JavaScript-Port fdlibm und erst dann die C-Version von fdlibm verwendet.

▍ Warum ist das ein Problem?


Der Punkt hier ist, dass all dies die FĂ€higkeit von JavaScript verringert, einheitliche Ergebnisse bei der Lösung von Problemen mit mathematischen Berechnungen zu erzielen. Dies ist besonders schwierig fĂŒr Data Science. Ich möchte, dass JavaScript besser fĂŒr Data Science-Berechnungen in einem Browser geeignet ist. DarĂŒber hinaus bedeutet die UnfĂ€higkeit, einheitliche Ergebnisse zu erzielen, die VerschĂ€rfung der Reproduzierbarkeitskrise , die fĂŒr alle Wissenschaften charakteristisch ist. Dies ist nicht zu erwĂ€hnen, dass einige andere JavaScript-Probleme auftreten, z. B. Funktionen zum Eingeben von Zahlen und das Fehlen einer weit verbreiteten Bibliothek fĂŒr die Arbeit mit Datenrahmen.

▍3. Spezialbibliotheken verwenden


Es gibt eine zuverlĂ€ssige Möglichkeit fĂŒr uns, die Berechnungen in JavaScript durchzufĂŒhren. Es besteht in der Verwendung spezialisierter Bibliotheken. Daher implementiert die stdlib- Bibliothek Berechnungen auf hoher Ebene, bei denen nur arithmetische Operationen verwendet werden. Arithmetische Berechnungen sind in den Spezifikationen vollstĂ€ndig beschrieben, sie sind Standard, sodass die von stdlib zurĂŒckgegebenen Ergebnisse unabhĂ€ngig von der Plattform, auf der der Code ausgefĂŒhrt wird, völlig einheitliche Ergebnisse liefern.

Dies wird auf Kosten der KomplexitĂ€t und Geschwindigkeit der Entscheidungen erreicht. Die stdlib-Methoden sind nicht so schnell wie die integrierten Methoden. Um "nur den Sinus zu zĂ€hlen", mĂŒssen Sie außerdem eine ganze Bibliothek mit dem Projekt verbinden.

Wenn Sie jedoch breiter denken, ist dies völlig normal. Die WebAssembly-Plattform bietet dem Programmierer beispielsweise keine Möglichkeit, mathematische Berechnungen auf hoher Ebene durchzufĂŒhren. In der Dokumentation dazu wird empfohlen, dass Sie Implementierungen der entsprechenden Mechanismen unabhĂ€ngig in Ihre eigenen Module aufnehmen:

„WebAssembly enthĂ€lt keine eigenen Implementierungen mathematischer Funktionen - wie sin, cos, exp, pow usw. Die Strategie von WebAssembly fĂŒr solche Funktionen besteht darin, Entwicklern die Implementierung als Bibliothekstools in der WebAssembly-Plattform selbst zu ermöglichen (beachten Sie, dass die sin- und cos-Anweisungen der x86-Plattform langsam und ungenau sind und heutzutage jedenfalls versuche es nicht zu benutzen). ”

So haben kompilierte Sprachen immer funktioniert: Beim Kompilieren eines in C geschriebenen Programms werden importierte Methoden math.hin das kompilierte Programm aufgenommen.

Epsilon-Wert verwenden


Wenn jemand die stdlib-Bibliothek nicht in sein JavaScript-Projekt aufnehmen möchte, um Berechnungen durchzufĂŒhren, sondern den Code testen muss, der einige komplexe Berechnungen ausfĂŒhrt, sollte er wahrscheinlich auf die Methode zurĂŒckgreifen, die bereits in der Bibliothek fĂŒr einfache Statistiken verwendet wird. Es geht darum, einen Wert zu verwenden epsilon, der die Grenzen definiert, innerhalb derer die Unterschiede in den Zahlen nicht berĂŒcksichtigt werden. Wenn wir die Optionen fĂŒr die Verwendung des Epsilon-Symbols in der Mathematik betrachten, können wir sagen, dass ich es als „willkĂŒrlichen kleinen positiven Wert“ spreche. Simple-Statistics verwendet den Epsilon-Wert gleich 0.0001.

Wenn Sie herausfinden mĂŒssen, ob zwei Zahlen gleich sind, wird eine Bedingung des Formulars ĂŒberprĂŒftMath.abs(result — expected) < epsilon. Wenn sich herausstellt, dass diese Bedingung erfĂŒllt ist, können wir sagen, dass die Differenz zwischen den Zahlen in den angegebenen Bereich fĂ€llt, und sie als gleich betrachten.

ErgÀnzungen


▍ Genauigkeit


Kommentatoren auf Twitter gaben an, dass die Abweichungen in den im Beispiel erhaltenen Ergebnissen außerhalb des Bereichs signifikanter Ziffern der Gleitkommazahl liegen. Aus technischer Sicht ist dies korrekt, und dies bedeutet, dass Sie einen genaueren Weg finden können, um Zahlen zu vergleichen, als den, der den Wert verwendet epsilon. In der Praxis jedoch die gleiche Geschichte - die Zahlen am Ende der Zahl wirken sich auf das Ergebnis aus und fĂŒhren zu Ungenauigkeiten im Endergebnis. DarĂŒber hinaus sind die hier angegebenen Beispiele nicht erschöpfend. Tatsache ist, dass die Implementierungsfunktionen von JavaScript-Interpreten, ohne von der Spezifikation abzuweichen, dazu fĂŒhren können, dass bei den meisten numerischen Ergebnissen Unterschiede auftreten.

▍JavaScript


Ich möchte JavaScript nicht kritisieren. Ich glaube, dass JavaScript einen berechtigten Kompromiss eingegangen ist, unter BerĂŒcksichtigung der Unsicherheit der Zukunft und der Anzahl der Plattformen, auf denen Sprachimplementierungen erstellt werden. Ich muss sagen, es ist sehr schwierig, JavaScript und andere Sprachen direkt zu vergleichen. Der Punkt ist das JavaScript-Ökosystem. Die Tatsache, dass es gleichzeitig viele Dolmetscher derselben Sprache gibt, ist fĂŒr andere Sprachen völlig untypisch. Dies ist außerdem eine der HauptstĂ€rken von JavaScript. DarĂŒber hinaus kann man nicht ĂŒbersehen, dass dies PhĂ€nomene eines völlig anderen Plans sind als diejenigen, die in der Sprache selbst auftreten. Und JavaScript Ă€ndert sich im Laufe der Zeit und es erscheinen viele gute Dinge darin .

▍Stdlib oder Epsilon?


Ich glaube, dass es sich in der Praxis in den meisten FĂ€llen lohnt, den Ansatz zu verwenden, der die Verwendung des Epsilon-Werts impliziert. Die stdlib-Bibliothek ist ein wunderbar leistungsfĂ€higes Werkzeug, aber der Preis fĂŒr die Aufnahme einer zusĂ€tzlichen Bibliothek fĂŒr mathematische Berechnungen in das Projekt kann recht hoch sein. In den meisten FĂ€llen spielen kleine Abweichungen in den Berechnungsergebnissen fĂŒr Anwendungen keine Rolle.

Zusammenfassung


Hier möchte ich aus dem Vorstehenden Schlussfolgerungen ziehen und einige Gedanken teilen.

  1. , , , . . — « ». , , , Math.sin , , , . , , , , , . , , , .
  2. . , , Node.js, , , simply-statistics. , , , , . — .
  3. , . V8, , . , . , , , .

Liebe Leser! Haben Sie beim Upgrade auf neue Versionen von Node.js Probleme mit Änderungen der Berechnungsergebnisse festgestellt?


All Articles