La guida essenziale al tipo di dati più recente di JavaScript: BigInt

Pubblicato: 2022-03-10
Riepilogo rapido ↬ In JavaScript, il tipo Number non può rappresentare in modo sicuro valori interi maggiori di 2 53 . Questa limitazione ha costretto gli sviluppatori a utilizzare soluzioni alternative inefficienti e librerie di terze parti. BigInt è un nuovo tipo di dati destinato a risolverlo.

Il tipo di dati BigInt mira a consentire ai programmatori JavaScript di rappresentare valori interi più grandi dell'intervallo supportato dal tipo di dati Number . La capacità di rappresentare numeri interi con precisione arbitraria è particolarmente importante quando si eseguono operazioni matematiche su numeri interi di grandi dimensioni. Con BigInt , l'overflow di numeri interi non sarà più un problema.

Inoltre, puoi lavorare in sicurezza con timestamp ad alta risoluzione, ID interi grandi e altro senza dover utilizzare una soluzione alternativa. BigInt è attualmente una proposta di fase 3. Una volta aggiunto alla specifica, diventerà il secondo tipo di dati numerico in JavaScript, portando a otto il numero totale di tipi di dati supportati:

  • booleano
  • Nullo
  • Non definito
  • Numero
  • BigInt
  • Corda
  • Simbolo
  • Oggetto

In questo articolo, daremo una buona occhiata a BigInt e vedremo come può aiutare a superare i limiti del tipo Number in JavaScript.

Il problema

La mancanza di un tipo intero esplicito in JavaScript è spesso sconcertante per i programmatori provenienti da altri linguaggi. Molti linguaggi di programmazione supportano più tipi numerici come float, double, integer e bignum, ma non è il caso di JavaScript. In JavaScript, tutti i numeri sono rappresentati in formato a virgola mobile a 64 bit a precisione doppia, come definito dallo standard IEEE 754-2008.

Altro dopo il salto! Continua a leggere sotto ↓

In questo standard, gli interi molto grandi che non possono essere rappresentati esattamente vengono arrotondati automaticamente. Per essere precisi, il tipo Number in JavaScript può rappresentare in modo sicuro solo numeri interi compresi tra -9007199254740991 (-(2 53 -1)) e 9007199254740991 (2 53 -1). Qualsiasi valore intero che non rientra in questo intervallo può perdere precisione.

Questo può essere facilmente esaminato eseguendo il seguente codice:

 console.log(9999999999999999); // → 10000000000000000

Questo numero intero è maggiore del numero più grande che JavaScript può rappresentare in modo affidabile con la primitiva Number . Pertanto, è arrotondato. Arrotondamenti imprevisti possono compromettere l'affidabilità e la sicurezza di un programma. Ecco un altro esempio:

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

JavaScript fornisce la costante Number.MAX_SAFE_INTEGER che consente di ottenere rapidamente il massimo numero intero sicuro in JavaScript. Allo stesso modo, puoi ottenere il numero intero sicuro minimo usando la costante 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

La soluzione

Come soluzione alternativa a queste limitazioni, alcuni sviluppatori JavaScript rappresentano numeri interi grandi utilizzando il tipo String . L'API di Twitter, ad esempio, aggiunge una versione stringa di ID agli oggetti quando risponde con JSON. Inoltre, sono state sviluppate numerose librerie come bignumber.js per semplificare il lavoro con numeri interi grandi.

Con BigInt , le applicazioni non necessitano più di una soluzione alternativa o di una libreria per rappresentare in modo sicuro numeri interi oltre Number.MAX_SAFE_INTEGER e Number.Min_SAFE_INTEGER . È ora possibile eseguire operazioni aritmetiche su numeri interi di grandi dimensioni in JavaScript standard senza rischiare la perdita di precisione. Il vantaggio aggiuntivo dell'utilizzo di un tipo di dati nativo su una libreria di terze parti è una migliore prestazione in fase di esecuzione.

Per creare un BigInt , aggiungi semplicemente n alla fine di un intero. Confrontare:

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

In alternativa, puoi chiamare il BigInt() :

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

I letterali BigInt possono anche essere scritti in notazione binaria, ottale o esadecimale:

 // 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

Tieni presente che non puoi utilizzare l'operatore di uguaglianza rigorosa per confrontare un BigInt con un numero normale perché non sono dello stesso tipo:

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

Invece, puoi usare l'operatore di uguaglianza, che esegue la conversione del tipo implicito prima di confrontare i suoi operandi:

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

Tutti gli operatori aritmetici possono essere utilizzati su BigInt s ad eccezione dell'operatore unario più ( + ):

 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

Il motivo per cui l'operatore unario più ( + ) non è supportato è che alcuni programmi possono fare affidamento sull'invariante che + produce sempre un Number o genera un'eccezione. Anche la modifica del comportamento di + interromperebbe il codice asm.js.

Naturalmente, se utilizzati con gli operandi BigInt , gli operatori aritmetici devono restituire un valore BigInt . Pertanto, il risultato dell'operatore di divisione ( / ) viene automaticamente troncato. Per esempio:

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

Conversione implicita del tipo

Poiché la conversione del tipo implicito potrebbe perdere informazioni, non sono consentite operazioni miste tra BigInt Number s. Quando si mischiano interi grandi e numeri a virgola mobile, il valore risultante potrebbe non essere rappresentabile accuratamente da BigInt o Number . Considera il seguente esempio:

 (9007199254740992n + 1n) + 0.5

Il risultato di questa espressione è esterno al dominio di BigInt e Number . Un Number con una parte frazionaria non può essere convertito accuratamente in un BigInt . E un BigInt maggiore di 2 53 non può essere convertito accuratamente in un Number .

A causa di questa restrizione, non è possibile eseguire operazioni aritmetiche con una combinazione di operandi Number e BigInt . Inoltre, non puoi passare un BigInt alle API Web e alle funzioni JavaScript integrate che prevedono un Number . Il tentativo di farlo causerà un TypeError :

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

Si noti che gli operatori relazionali non seguono questa regola, come mostrato in questo esempio:

 10n > 5; // → true

Se vuoi eseguire calcoli aritmetici con BigInt e Number , devi prima determinare il dominio in cui deve essere eseguita l'operazione. Per farlo, converti semplicemente uno degli operandi chiamando Number() o BigInt() :

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

Quando viene rilevato in un contesto Boolean , BigInt viene trattato in modo simile a Number . In altre parole, un BigInt è considerato un valore veritiero purché non sia 0n :

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

Non si verifica alcuna conversione di tipo implicita tra i tipi BigInt e Number durante l'ordinamento di una matrice:

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

Operatori bit per bit come | , & , << , >> e ^ operano su BigInt s in modo simile a Number s. I numeri negativi sono interpretati come complemento a due di lunghezza infinita. Non sono ammessi operandi misti. Ecco alcuni esempi:

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

Il costruttore BigInt

Come con altri tipi primitivi, è possibile creare un BigInt utilizzando una funzione di costruzione. L'argomento passato a BigInt() viene automaticamente convertito in BigInt , se possibile:

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

I tipi di dati e i valori che non possono essere convertiti generano un'eccezione:

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

Puoi eseguire direttamente operazioni aritmetiche su un BigInt creato utilizzando un costruttore:

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

Se utilizzati come operandi dell'operatore di uguaglianza rigorosa, i BigInt creati utilizzando un costruttore vengono trattati in modo simile a quelli normali:

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

Funzioni di libreria

JavaScript fornisce due funzioni di libreria per rappresentare i valori BigInt come interi con o senza segno:

  • BigInt.asUintN(width, BigInt) : avvolge un BigInt tra 0 e 2 width -1
  • BigInt.asIntN(width, BigInt) : avvolge un BigInt tra -2 width-1 e 2 width-1 -1

Queste funzioni sono particolarmente utili quando si eseguono operazioni aritmetiche a 64 bit. In questo modo puoi rimanere all'interno dell'intervallo previsto.

Supporto del browser e transpiling

Al momento in cui scrivo, Chrome +67 e Opera +54 supportano completamente il tipo di dati BigInt . Sfortunatamente, Edge e Safari non l'hanno ancora implementato. Firefox non supporta BigInt per impostazione predefinita, ma può essere abilitato impostando javascript.options.bigint su true in about:config . Un elenco aggiornato dei browser supportati è disponibile su Posso usare….

Sfortunatamente, la traspilazione di BigInt è un processo estremamente complicato, che comporta pesanti penalizzazioni in termini di prestazioni in fase di esecuzione. È inoltre impossibile eseguire direttamente il polyfill BigInt perché la proposta modifica il comportamento di diversi operatori esistenti. Per ora, un'alternativa migliore consiste nell'usare la libreria JSBI, che è un'implementazione JavaScript puro della proposta BigInt .

Questa libreria fornisce un'API che si comporta esattamente come il BigInt nativo. Ecco come puoi usare 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 vantaggio dell'utilizzo di JSBI è che una volta migliorato il supporto del browser, non sarà necessario riscrivere il codice. Invece, puoi compilare automaticamente il tuo codice JSBI in codice BigInt nativo utilizzando un plug-in babel. Inoltre, le prestazioni di JSBI sono alla pari con le implementazioni native di BigInt . Puoi aspettarti presto un supporto del browser più ampio per BigInt .

Conclusione

BigInt è un nuovo tipo di dati destinato all'uso quando i valori interi sono maggiori dell'intervallo supportato dal tipo di dati Number . Questo tipo di dati ci consente di eseguire in sicurezza operazioni aritmetiche su numeri interi di grandi dimensioni, rappresentare timestamp ad alta risoluzione, utilizzare ID di numeri interi grandi e altro senza la necessità di utilizzare una libreria.

È importante tenere presente che non è possibile eseguire operazioni aritmetiche con una combinazione di operandi Number e BigInt . Sarà necessario determinare il dominio in cui eseguire l'operazione convertendo in modo esplicito uno degli operandi. Inoltre, per motivi di compatibilità, non è consentito utilizzare l'operatore unario più ( + ) su un BigInt .

Cosa ne pensi? Trovi utile BigInt ? Fateci sapere nei commenti!