JavaScript: Das große Ganze gut warum


Vor nicht allzu langer Zeit hatte JavaScript einen neuen primitiven BigInt- Datentyp für die Arbeit mit Zahlen mit beliebiger Genauigkeit. Das notwendige Minimum an Informationen wurde bereits über Motivation und Anwendungsfälle erzählt / übersetzt . Und ich möchte der übertriebenen lokalen "Explizität" beim Typcasting und dem unerwarteten TypeError etwas mehr Aufmerksamkeit schenken . Werden wir schimpfen oder verstehen und (wieder) vergeben?

Implizit wird explizit?


In einer Sprache, an die implizite Typkonvertierung seit langem gewöhnt ist, ist sie zu einem Meme fast jeder Konferenz geworden, und nur wenige Menschen sind von solchen Komplikationen überrascht wie:

1 + {}; // '1[object Object]' 1 + [[0]]; // '10' 1 + new Date; // '1Fri Feb 08 2019 00:32:57 GMT+0300 (,  )' 1 - new Date; // -1549616425060 ... 

Wir bekommen plötzlich einen TypeError, der versucht, zwei scheinbar NUMMERN hinzuzufügen:

 1 + 1n; // TypeError: Cannot mix BigInt and other types, use explicit conversions 

Und wenn die früheren Erfahrungen mit impliziten Aussagen nicht zu einem Zusammenbruch beim Erlernen der Sprache geführt haben, gibt es eine zweite Chance, das ECMA-Lehrbuch zusammenzubrechen und wegzuwerfen und in Java zu wechseln.

Darüber hinaus „trollt“ die Sprache weiterhin die Entwickler:

 1n + '1'; // '11' 

Oh ja, vergessen Sie nicht den unären + Operator:

 +1n; // TypeError: Cannot convert a BigInt value to a number Number(1n); // 1 

Kurz gesagt, wir können BigInt und Number nicht in Operationen mischen. Daher wird nicht empfohlen, "große Ganzzahlen" zu verwenden, wenn 2 ^ 53-1 ( MAX_SAFE_INTEGER ) für unsere Zwecke ausreicht.

Schlüsselentscheidung


Ja, dies war die Hauptentscheidung dieser Innovation. Wenn Sie vergessen, dass dies JavaScript ist, ist alles so logisch: Diese impliziten Konvertierungen tragen zum Informationsverlust bei.

Wenn wir zwei Werte unterschiedlicher numerischer Typen (große Ganzzahlen und Gleitkommazahlen) hinzufügen, liegt der mathematische Wert des Ergebnisses möglicherweise außerhalb ihres Bereichs möglicher Werte. Beispielsweise kann der Wert des Ausdrucks (2n ** 53n + 1n) + 0,5 durch keinen dieser Typen genau dargestellt werden. Dies ist keine Ganzzahl mehr, sondern eine reelle Zahl, aber ihre Genauigkeit wird durch das float64-Format nicht mehr garantiert:

 2n ** 53n + 1n; // 9007199254740993n Number(2n ** 53n + 1n) + 0.5; // 9007199254740992 

In den meisten dynamischen Sprachen, in denen Typen für Ganzzahlen und Gleitkommazahlen dargestellt werden, werden die ersten als 1 und die zweiten als 1,0 geschrieben . Während arithmetischer Operationen bei Vorhandensein eines Dezimaltrennzeichens im Operanden können wir daher schließen, dass die Genauigkeit von float in Berechnungen akzeptabel ist. Aber JavaScript gehört nicht dazu, und 1 ist ein Float! Und dies bedeutet, dass die Berechnung von 2n ** 53n + 1 float 2 ^ 53 zurückgibt . Was wiederum die Schlüsselfunktionalität von BigInt bricht:

 2 ** 53 === 2 ** 53 + 1; // true 

Es gibt auch keinen Grund, über die Implementierung des "numerischen Turms" zu sprechen, da es Ihnen aus demselben Grund nicht gelingt, die vorhandene Nummer als allgemeinen numerischen Datentyp zu verwenden.

Um dieses Problem zu vermeiden, wurde die implizite Umwandlung zwischen Number und BigInt in Operationen verboten. Infolgedessen kann die „große Ganzzahl“ nicht sicher in eine JavaScript- oder Web-API-Funktion umgewandelt werden, bei der die übliche Anzahl erwartet wird:

 Math.max(1n, 10n); // TypeError 

Sie müssen explizit einen der beiden Typen mit Number () oder BigInt () auswählen .

Darüber hinaus gibt es für Operationen mit gemischten Typen eine Erklärung für eine komplexe Implementierung oder einen Leistungsverlust, die bei Kompromiss-Sprachinnovationen häufig vorkommt.

Dies gilt natürlich für implizite numerische Konvertierungen mit anderen Grundelementen:

 1 + true; // 2 1n + true; // TypeError 1 + null; // 1 1n + null; // TypeError 

Die folgenden (bereits) Verkettungen funktionieren jedoch, da das erwartete Ergebnis eine Zeichenfolge ist:

 1n + [0]; // '10' 1n + {}; // '1[object Object]' 1n + (_ => 1); // '1_ => 1' 

Eine weitere Ausnahme bilden Vergleichsoperatoren (wie < , > und == ) zwischen Number und BigInt . Es gibt auch keinen Genauigkeitsverlust, da das Ergebnis ein Boolescher Wert ist.

Wenn Sie sich an den vorherigen neuen Symbol- Datentyp erinnern, scheint TypeError nicht mehr so ​​radikal zu sein?

 Symbol() + 1; // TypeError: Cannot convert a Symbol value to a number 

Und ja, aber nein. In der Tat ist das konzeptionelle Symbol überhaupt keine Zahl, sondern ein Ganzes - sehr viel:

  1. Es ist höchst unwahrscheinlich, dass das Symbol in eine solche Situation gerät. Dies ist jedoch sehr verdächtig und TypeError ist hier durchaus angemessen.
  2. Es ist sehr wahrscheinlich und üblich, dass sich das „große Ganze“ in Operationen als einer der Operanden herausstellt, wenn wirklich nichts falsch ist.

Der Operator unary + löst aufgrund eines Kompatibilitätsproblems mit asm.js eine Ausnahme aus, bei der Number erwartet wird. Das unäre Plus kann nicht wie Big mit BigInt arbeiten , da in diesem Fall der vorherige asm.js-Code mehrdeutig wird.

Alternatives Angebot


Trotz der relativen Einfachheit und „Sauberkeit“ der Implementierung von BigInt betont Axel Rauschmeyer den Mangel an Innovation. Es ist nämlich seine nur teilweise Abwärtskompatibilität mit der vorhandenen Nummer und den folgenden:
Verwenden Sie Zahlen für bis zu 53-Bit-Ints. Verwenden Sie Ganzzahlen, wenn Sie mehr Bits benötigen
Als Alternative schlug er Folgendes vor .

Lassen Sie Number zum Supertyp für das neue Int und Double werden :

  • typeof 123.0 === 'number' und Number.isDouble (123.0) === true
  • typeof 123 === 'number' und Number.isInt (123) === true

Mit neuen Funktionen für die Konvertierungen von Number.asInt () und Number.asDouble () . Und natürlich mit Überlastung des Bedieners und den notwendigen Abgüssen:

  • Int × Double = Double (Besetzung)
  • Double × Int = Double (mit Besetzung)
  • Double × Double = Double
  • Int × Int = Int (alle Operatoren außer Division)

Interessanterweise wird dieser Satz in der vereinfachten Version (zunächst) verwaltet, ohne der Sprache neue Typen hinzuzufügen. Stattdessen wird die Definition des Nummerntyps erweitert: Zusätzlich zu allen möglichen 64-Bit-Zahlen mit doppelter Genauigkeit (IEEE 754-2008) enthält die Zahl jetzt alle Ganzzahlen. Infolgedessen sind die "ungenaue Nummer" 123.0 und die "genaue Nummer" 123 separate Nummern des Typs mit einer einzelnen Nummer .

Es sieht sehr vertraut und vernünftig aus. Dies ist jedoch eine ernsthafte Aktualisierung der vorhandenen Nummer, die mit größerer Wahrscheinlichkeit das Web und seine Tools "kaputt macht":

  • Es gibt einen Unterschied zwischen 1 und 1,0 , der vorher nicht da war. Der vorhandene Code verwendet sie austauschbar, was nach dem Upgrade zu Verwirrung führen kann (im Gegensatz zu Sprachen, in denen dieser Unterschied ursprünglich vorhanden war).
  • Es gibt einen Effekt, wenn 1 === 1.0 (es soll ein Upgrade sein) und gleichzeitig Number.isDouble (1)! == Number.isDouble (1.0) : Auch hier ist es so.
  • Die „Besonderheit“ der Gleichheit 2 ^ 53 und 2 ^ 53 + 1 verschwindet, wodurch der darauf beruhende Code gebrochen wird.
  • Das gleiche Kompatibilitätsproblem mit asm.js und mehr.

Daher haben wir am Ende eine Kompromisslösung in Form eines neuen separaten Datentyps. Es ist nur hervorzuheben, dass auch eine andere Option in Betracht gezogen und diskutiert wurde .

Wenn Sie auf zwei Stühlen sitzen


Tatsächlich beginnt der Kommentar des Ausschusses mit den Worten:
Finden Sie ein Gleichgewicht zwischen der Wahrung der Benutzerintuition und der Wahrung der Präzision

Einerseits wollte ich der Sprache endlich etwas „Exaktes“ hinzufügen. Und andererseits, um das für viele Entwickler bereits bekannte Verhalten beizubehalten.

Es ist nur so, dass dieses "genaue" nicht hinzugefügt werden kann, weil Sie es nicht brechen können: Mathematik, Ergonomie der Sprache, asm.js, die Möglichkeit einer weiteren Erweiterung des Typensystems, Produktivität und letztendlich das Web selbst! Und Sie können nicht alles gleichzeitig brechen, was zum selben Ergebnis führt.

Und Sie können die Intuition der Sprachbenutzer nicht brechen, über die natürlich auch heftig diskutiert wurde . Stimmt, hat es geklappt?

Source: https://habr.com/ru/post/de439402/


All Articles