Ghidul esențial pentru cel mai nou tip de date JavaScript: BigInt

Publicat: 2022-03-10
Rezumat rapid ↬ În JavaScript, tipul Number nu poate reprezenta în siguranță valori întregi mai mari de 2 53 . Această limitare i-a forțat pe dezvoltatori să folosească soluții ineficiente și biblioteci terță parte. BigInt este un nou tip de date menit să rezolve acest lucru.

Tipul de date BigInt își propune să permită programatorilor JavaScript să reprezinte valori întregi mai mari decât intervalul acceptat de tipul de date Number . Abilitatea de a reprezenta numere întregi cu precizie arbitrară este deosebit de importantă atunci când se efectuează operații matematice pe numere întregi mari. Cu BigInt , depășirea întregului nu va mai fi o problemă.

În plus, puteți lucra în siguranță cu marcaje temporale de înaltă rezoluție, ID-uri întregi mari și multe altele, fără a fi nevoie să utilizați o soluție. BigInt este în prezent o propunere de etapa 3. Odată adăugat la specificație, va deveni al doilea tip de date numerice din JavaScript, ceea ce va aduce numărul total de tipuri de date acceptate la opt:

  • boolean
  • Nul
  • Nedefinit
  • Număr
  • BigInt
  • Şir
  • Simbol
  • Obiect

În acest articol, vom arunca o privire atentă la BigInt și vom vedea cum poate ajuta la depășirea limitărilor tipului Number din JavaScript.

Problema

Lipsa unui tip întreg explicit în JavaScript este adesea derutant pentru programatorii care provin din alte limbi. Multe limbaje de programare acceptă mai multe tipuri numerice, cum ar fi float, double, integer și bignum, dar nu este cazul JavaScript. În JavaScript, toate numerele sunt reprezentate în format de dublă precizie, pe 64 de biți, în virgulă mobilă, așa cum este definit de standardul IEEE 754-2008.

Mai multe după săritură! Continuați să citiți mai jos ↓

Conform acestui standard, numerele întregi foarte mari care nu pot fi reprezentate exact sunt rotunjite automat. Pentru a fi precis, tipul Number din JavaScript poate reprezenta în siguranță numai numere întregi între -9007199254740991 (-(2 53 -1)) și 9007199254740991 (2 53 -1). Orice valoare întreagă care se încadrează în acest interval poate pierde precizia.

Acest lucru poate fi examinat cu ușurință prin executarea următorului cod:

 console.log(9999999999999999); // → 10000000000000000

Acest număr întreg este mai mare decât cel mai mare număr pe care JavaScript îl poate reprezenta în mod fiabil cu primitiva Number . Prin urmare, este rotunjit. Rotunjirea neașteptată poate compromite fiabilitatea și securitatea unui program. Iată un alt exemplu:

 // notice the last digits 9007199254740992 === 9007199254740993; // → true

JavaScript oferă constanta Number.MAX_SAFE_INTEGER care vă permite să obțineți rapid numărul întreg maxim sigur în JavaScript. În mod similar, puteți obține numărul întreg minim sigur utilizând constanta Number.MIN_SAFE_INTEGER :

 const minInt = Number.MIN_SAFE_INTEGER; console.log(minInt); // → -9007199254740991 console.log(minInt - 5); // → -9007199254740996 // notice how this outputs the same value as above console.log(minInt - 4); // → -9007199254740996

Soluția

Ca o soluție pentru aceste limitări, unii dezvoltatori JavaScript reprezintă numere întregi mari folosind tipul String . API-ul Twitter, de exemplu, adaugă o versiune șir de ID-uri la obiecte atunci când răspunde cu JSON. În plus, o serie de biblioteci, cum ar fi bignumber.js, au fost dezvoltate pentru a facilita lucrul cu numere întregi mari.

Cu BigInt , aplicațiile nu mai au nevoie de o soluție sau o bibliotecă pentru a reprezenta în siguranță numerele întregi dincolo de Number.MAX_SAFE_INTEGER și Number.Min_SAFE_INTEGER . Operațiile aritmetice pe numere întregi mari pot fi acum efectuate în JavaScript standard fără a risca pierderea preciziei. Avantajul suplimentar al utilizării unui tip de date nativ față de o bibliotecă terță parte este o performanță mai bună la timp de rulare.

Pentru a crea un BigInt , pur și simplu adăugați n la sfârșitul unui număr întreg. Comparaţie:

 console.log(9007199254740995n); // → 9007199254740995n console.log(9007199254740995); // → 9007199254740996

Alternativ, puteți apela constructorul BigInt() :

 BigInt("9007199254740995"); // → 9007199254740995n

BigInt pot fi scrise și în notație binară, octală sau hexazecimală:

 // binary console.log(0b100000000000000000000000000000000000000000000000000011n); // → 9007199254740995n // hex console.log(0x20000000000003n); // → 9007199254740995n // octal console.log(0o400000000000000003n); // → 9007199254740995n // note that legacy octal syntax is not supported console.log(0400000000000000003n); // → SyntaxError

Rețineți că nu puteți utiliza operatorul de egalitate strictă pentru a compara un BigInt cu un număr obișnuit, deoarece nu sunt de același tip:

 console.log(10n === 10); // → false console.log(typeof 10n); // → bigint console.log(typeof 10); // → number

În schimb, puteți utiliza operatorul de egalitate, care efectuează conversie implicită de tip înainte de a compara operanzii săi:

 console.log(10n == 10); // → true

Toți operatorii aritmetici pot fi utilizați pe BigInt , cu excepția operatorului unar plus ( + ):

 10n + 20n; // → 30n 10n - 20n; // → -10n +10n; // → TypeError: Cannot convert a BigInt value to a number -10n; // → -10n 10n * 20n; // → 200n 20n / 10n; // → 2n 23n % 10n; // → 3n 10n ** 3n; // → 1000n let x = 10n; ++x; // → 11n --x; // → 10n

Motivul pentru care operatorul unar plus ( + ) nu este acceptat este că unele programe se pot baza pe invariantul care + produce întotdeauna un Number sau aruncă o excepție. Schimbarea comportamentului + ar sparge și codul asm.js.

Desigur, atunci când sunt utilizați cu operanzi BigInt , se așteaptă ca operatorii aritmetici să returneze o valoare BigInt . Prin urmare, rezultatul operatorului de împărțire ( / ) este trunchiat automat. De exemplu:

 25 / 10; // → 2.5 25n / 10n; // → 2n

Conversie implicită de tip

Deoarece conversia implicită de tip ar putea pierde informații, operațiunile mixte între BigInt și Number nu sunt permise. Când se amestecă numere întregi mari și numere în virgulă mobilă, este posibil ca valoarea rezultată să nu poată fi reprezentată cu precizie de BigInt sau Number . Luați în considerare următorul exemplu:

 (9007199254740992n + 1n) + 0.5

Rezultatul acestei expresii este în afara domeniului BigInt și Number . Un Number cu o parte fracțională nu poate fi convertit cu precizie într-un BigInt . Și un BigInt mai mare de 2 53 nu poate fi convertit cu precizie într-un Number .

Ca urmare a acestei restricții, nu este posibil să se efectueze operații aritmetice cu un amestec de operanzi Number și BigInt . De asemenea, nu puteți transmite un BigInt la API-urile web și la funcțiile JavaScript încorporate care așteaptă un Number . Încercarea de a face acest lucru va provoca o TypeError de tip:

 10 + 10n; // → TypeError Math.max(2n, 4n, 6n); // → TypeError

Rețineți că operatorii relaționali nu respectă această regulă, așa cum se arată în acest exemplu:

 10n > 5; // → true

Dacă doriți să efectuați calcule aritmetice cu BigInt și Number , mai întâi trebuie să determinați domeniul în care trebuie efectuată operația. Pentru a face asta, pur și simplu convertiți oricare dintre operanzi apelând Number() sau BigInt() :

 BigInt(10) + 10n; // → 20n // or 10 + Number(10n); // → 20

Când este întâlnit într-un context Boolean , BigInt este tratat similar cu Number . Cu alte cuvinte, un BigInt este considerat o valoare adevărată atâta timp cât nu este 0n :

 if (5n) { // this code block will be executed } if (0n) { // but this code block won't }

Nu are loc o conversie implicită de tip între BigInt și Number la sortarea unei matrice:

 const arr = [3n, 4, 2, 1n, 0, -1n]; arr.sort(); // → [-1n, 0, 1n, 2, 3n, 4]

Operatori pe biți, cum ar fi | , & , << , >> și ^ operează pe BigInt s într-un mod similar cu Number s. Numerele negative sunt interpretate ca complement a doi cu lungime infinită. Operanzii mixți nu sunt permisi. Aici sunt cateva exemple:

 90 | 115; // → 123 90n | 115n; // → 123n 90n | 115; // → TypeError

Constructorul BigInt

Ca și în cazul altor tipuri primitive, un BigInt poate fi creat folosind o funcție de constructor. Argumentul transmis către BigInt() este convertit automat într-un BigInt , dacă este posibil:

 BigInt("10"); // → 10n BigInt(10); // → 10n BigInt(true); // → 1n

Tipurile de date și valorile care nu pot fi convertite fac o excepție:

 BigInt(10.2); // → RangeError BigInt(null); // → TypeError BigInt("abc"); // → SyntaxError

Puteți efectua direct operații aritmetice pe un BigInt creat folosind un constructor:

 BigInt(10) * 10n; // → 100n

Când sunt utilizate ca operanzi ai operatorului de egalitate strictă, BigInt -urile create folosind un constructor sunt tratate similar cu cele obișnuite:

 BigInt(true) === 1n; // → true

Funcții de bibliotecă

JavaScript oferă două funcții de bibliotecă pentru reprezentarea valorilor BigInt ca numere întregi semnate sau nesemnate:

  • BigInt.asUintN(width, BigInt) : împachetează un BigInt între 0 și 2 lățime -1
  • BigInt.asIntN(width, BigInt) : include un BigInt între -2 width-1 și 2 width-1 -1

Aceste funcții sunt deosebit de utile atunci când se efectuează operații aritmetice pe 64 de biți. În acest fel, puteți rămâne în intervalul dorit.

Suport pentru browser și transpilare

La momentul scrierii acestui articol, Chrome +67 și Opera +54 acceptă pe deplin tipul de date BigInt . Din păcate, Edge și Safari nu l-au implementat încă. Firefox nu acceptă BigInt în mod implicit, dar poate fi activat setând javascript.options.bigint la true în about:config . O listă actualizată a browserelor acceptate este disponibilă pe Pot să folosesc...

Din nefericire, transpilarea BigInt este un proces extrem de complicat, care implică o penalizare semnificativă a performanței la timpul de execuție. De asemenea, este imposibil să polifill direct BigInt deoarece propunerea modifică comportamentul mai multor operatori existenți. Deocamdată, o alternativă mai bună este utilizarea bibliotecii JSBI, care este o implementare pur-JavaScript a propunerii BigInt .

Această bibliotecă oferă un API care se comportă exact la fel ca BigInt nativ. Iată cum puteți utiliza JSBI:

 import JSBI from './jsbi.mjs'; const b1 = JSBI.BigInt(Number.MAX_SAFE_INTEGER); const b2 = JSBI.BigInt('10'); const result = JSBI.add(b1, b2); console.log(String(result)); // → '9007199254741001'

Un avantaj al utilizării JSBI este că, odată ce suportul browserului se îmbunătățește, nu va trebui să rescrieți codul. În schimb, puteți compila automat codul dvs. JSBI în codul nativ BigInt folosind un plugin babel. În plus, performanța JSBI este la egalitate cu implementările native BigInt . Vă puteți aștepta în curând la o asistență mai largă pentru browser pentru BigInt .

Concluzie

BigInt este un nou tip de date destinat utilizării atunci când valorile întregi sunt mai mari decât intervalul acceptat de tipul de date Number . Acest tip de date ne permite să efectuăm în siguranță operații aritmetice pe numere întregi mari, să reprezentăm marcaje temporale de înaltă rezoluție, să folosim ID-uri întregi mari și multe altele, fără a fi nevoie să folosim o bibliotecă.

Este important să rețineți că nu puteți efectua operații aritmetice cu un amestec de operanzi Number și BigInt . Va trebui să determinați domeniul în care ar trebui efectuată operația prin conversia explicită a oricăruia dintre operanzi. Mai mult, din motive de compatibilitate, nu aveți voie să utilizați operatorul unar plus ( + ) pe un BigInt .

Ce crezi? Vi se pare util BigInt ? Spune-ne în comentarii!