Guide complet sur BigInt en JavaScript. Gérez les calculs sur de grands nombres avec précision au-delà de Number.MAX_SAFE_INTEGER pour la crypto et la fintech.
Arithmétique BigInt en JavaScript : Une exploration approfondie des calculs sur de grands nombres et de la gestion de la précision
Pendant de nombreuses années, les développeurs JavaScript ont été confrontés à une limitation silencieuse mais importante : l'incapacité de représenter nativement et avec précision de très grands nombres entiers. Tous les nombres en JavaScript étaient traditionnellement représentés comme des nombres à virgule flottante double précision IEEE 754, ce qui impose un plafond à la précision des entiers. Lorsque les calculs impliquaient des nombres plus grands que ce qui pouvait être contenu en toute sécurité, les développeurs devaient recourir à des bibliothèques tierces. Cela a changé avec l'introduction de BigInt dans ECMAScript 2020 (ES11), une fonctionnalité révolutionnaire qui a intégré les entiers à précision arbitraire au cœur du langage.
Ce guide complet est conçu pour un public mondial de développeurs. Nous explorerons les problèmes que BigInt résout, comment l'utiliser pour une arithmétique précise, ses applications réelles dans des domaines comme la cryptographie et la finance, et les pièges courants à éviter. Que vous construisiez une plateforme fintech, une simulation scientifique ou que vous interagissiez avec des systèmes utilisant des identifiants 64 bits, la compréhension de BigInt est essentielle pour le développement JavaScript moderne.
Le plafond de verre du type `Number` de JavaScript
Avant de pouvoir apprécier la solution, nous devons d'abord comprendre le problème. Le type standard Number de JavaScript, bien que polyvalent, présente une limitation fondamentale en matière de précision des entiers. Il ne s'agit pas d'un bug ; c'est une conséquence directe de sa conception basée sur la norme IEEE 754 pour l'arithmétique en virgule flottante.
Comprendre `Number.MAX_SAFE_INTEGER`
Le type Number ne peut représenter en toute sécurité que les entiers jusqu'à une certaine valeur. Ce seuil est exposé en tant que propriété statique : Number.MAX_SAFE_INTEGER.
Sa valeur est 9,007,199,254,740,991, soit 253 - 1. Pourquoi ce nombre spécifique ? Dans les 64 bits utilisés pour un flottant double précision, 52 bits sont dédiés à la mantisse (les chiffres significatifs), un bit pour le signe et 11 bits pour l'exposant. Cette structure permet une très large plage de valeurs mais limite la représentation contiguë et sans trou des entiers.
Voyons ce qui se passe lorsque nous essayons de dépasser cette limite :
const maxSafeInt = Number.MAX_SAFE_INTEGER;
console.log(maxSafeInt); // 9007199254740991
const oneMore = maxSafeInt + 1;
console.log(oneMore); // 9007199254740992
const twoMore = maxSafeInt + 2;
console.log(twoMore); // 9007199254740992 - Uh oh!
console.log(oneMore === twoMore); // true
Comme vous pouvez le voir, une fois que nous franchissons le seuil, le système numérique perd sa capacité à représenter chaque entier consécutif. maxSafeInt + 1 et maxSafeInt + 2 s'évaluent à la même valeur. Cette perte de précision silencieuse peut entraîner des bugs catastrophiques dans les applications qui dépendent d'une arithmétique entière exacte, comme les calculs financiers ou la gestion de grands identifiants de base de données.
Quand est-ce que cela a de l'importance ?
Cette limitation n'est pas seulement une curiosité théorique. Elle a des conséquences importantes dans le monde réel :
- Identifiants de base de données : De nombreux systèmes de bases de données modernes, comme PostgreSQL, utilisent un type entier de 64 bits (
BIGINT) pour les clés primaires. Ces identifiants peuvent facilement dépasserNumber.MAX_SAFE_INTEGER. Lorsqu'un client JavaScript récupère cet ID, il peut être arrondi de manière incorrecte, entraînant une corruption des données ou l'incapacité de récupérer le bon enregistrement. - Intégrations d'API : Des services comme Twitter (maintenant X) utilisent des entiers de 64 bits appelés "Snowflakes" pour les identifiants de tweets. La gestion correcte de ces identifiants dans un frontend JavaScript nécessite une attention particulière.
- Cryptographie : Les opérations cryptographiques impliquent fréquemment des calculs avec des nombres premiers extrêmement grands, bien au-delà de la capacité du type
Numberstandard. - Horodatages de haute précision : Certains systèmes fournissent des horodatages avec une précision à la nanoseconde, souvent représentés par un décompte d'entiers 64 bits depuis une époque. Le stockage de cette valeur dans un
Numberstandard tronquerait sa précision.
Voici BigInt : La solution pour les entiers à précision arbitraire
BigInt a été introduit spécifiquement pour résoudre ce problème. C'est un type primitif numérique distinct en JavaScript qui peut représenter des entiers avec une précision arbitraire. Cela signifie qu'un BigInt n'est pas limité par un nombre fixe de bits ; il peut s'agrandir ou se réduire pour accommoder la valeur qu'il contient, limité uniquement par la mémoire disponible dans le système hôte.
Créer un BigInt
Il y a deux manières principales de créer une valeur BigInt :
- Ajouter `n` à un littéral entier : C'est la méthode la plus simple et la plus courante.
- Utiliser la fonction constructeur `BigInt()` : C'est utile pour convertir des chaînes de caractères ou des Numbers en BigInts.
Voici quelques exemples :
// Using the 'n' suffix
const aLargeNumber = 9007199254740991n;
const anEvenLargerNumber = 1234567890123456789012345678901234567890n;
// Using the BigInt() constructor
const fromString = BigInt("98765432109876543210");
const fromNumber = BigInt(100); // Creates 100n
// Let's verify their type
console.log(typeof aLargeNumber); // "bigint"
console.log(typeof fromString); // "bigint"
Note importante : Vous ne pouvez pas utiliser l'opérateur `new` avec `BigInt()`, car c'est un type primitif, pas un objet. `new BigInt()` lèvera une `TypeError`.
Arithmétique de base avec BigInt
BigInt prend en charge les opérateurs arithmétiques standard que vous connaissez, mais ils se comportent strictement dans le domaine des entiers.
Addition, Soustraction et Multiplication
Ces opérateurs fonctionnent exactement comme vous vous y attendez, mais avec la capacité de gérer des nombres énormes sans perte de précision.
const num1 = 12345678901234567890n;
const num2 = 98765432109876543210n;
// Addition
console.log(num1 + num2); // 111111111011111111100n
// Subtraction
console.log(num2 - num1); // 86419753208641975320n
// Multiplication
console.log(num1 * 2n); // 24691357802469135780n
Division (`/`)
C'est ici que le comportement de BigInt diffère de manière significative de la division standard de Number. Parce que les BigInts ne peuvent représenter que des nombres entiers, le résultat d'une division est toujours tronqué vers zéro (la partie fractionnaire est rejetée).
const dividend = 10n;
const divisor = 3n;
console.log(dividend / divisor); // 3n (not 3.333...)
const negativeDividend = -10n;
console.log(negativeDividend / divisor); // -3n
// For comparison with Number division
console.log(10 / 3); // 3.3333333333333335
Cette division entière est cruciale. Si vous avez besoin d'effectuer des calculs qui requièrent une précision décimale, BigInt n'est pas le bon outil. Vous devrez vous tourner vers des bibliothèques comme `Decimal.js` ou gérer la partie décimale manuellement (par exemple, en travaillant avec la plus petite unité monétaire dans les calculs financiers).
Reste (`%`) et Exponentiation (`**`)
L'opérateur de reste (%) et l'opérateur d'exponentiation (**) fonctionnent également comme prévu avec les valeurs BigInt.
console.log(10n % 3n); // 1n
console.log(-10n % 3n); // -1n
// Exponentiation can create truly massive numbers
const base = 2n;
const exponent = 100n;
const hugeNumber = base ** exponent;
console.log(hugeNumber); // 1267650600228229401496703205376n
La règle stricte : Ne pas mélanger `BigInt` et `Number`
L'une des règles les plus importantes à retenir lorsque l'on travaille avec BigInt est que vous ne pouvez pas mélanger des opérandes BigInt et Number dans la plupart des opérations arithmétiques. Tenter de le faire résultera en une TypeError.
Ce choix de conception était intentionnel. Il empêche les développeurs de perdre accidentellement de la précision lorsqu'un BigInt est implicitement converti en Number. Le langage vous oblige à être explicite sur vos intentions.
const myBigInt = 100n;
const myNumber = 50;
try {
const result = myBigInt + myNumber; // This will fail
} catch (error) {
console.error(error); // TypeError: Cannot mix BigInt and other types, use explicit conversions
}
La bonne approche : la conversion explicite
Pour effectuer une opération entre un BigInt et un Number, vous devez explicitement convertir l'un vers le type de l'autre.
const myBigInt = 100n;
const myNumber = 50;
// Convert the Number to a BigInt
const result1 = myBigInt + BigInt(myNumber);
console.log(result1); // 150n
// Convert the BigInt to a Number (use with caution!)
const result2 = Number(myBigInt) + myNumber;
console.log(result2); // 150
Avertissement : Convertir un BigInt en Number en utilisant `Number()` est dangereux si la valeur du BigInt est en dehors de la plage des entiers sûrs. Cela peut réintroduire les erreurs de précision mêmes que BigInt est conçu pour éviter.
const veryLargeBigInt = 9007199254740993n;
const convertedToNumber = Number(veryLargeBigInt);
console.log(veryLargeBigInt); // 9007199254740993n
console.log(convertedToNumber); // 9007199254740992 - Precision lost!
La règle générale est la suivante : si vous travaillez avec des entiers potentiellement grands, restez dans l'écosystème BigInt pour tous vos calculs. Ne reconvertissez en Number que si vous êtes certain que la valeur se situe dans la plage de sécurité.
Opérateurs de comparaison et logiques
Alors que les opérateurs arithmétiques sont stricts sur le mélange des types, les opérateurs de comparaison et logiques sont plus souples.
Comparaisons relationnelles (`>`, `<`, `>=`, `<=`)
Vous pouvez comparer en toute sécurité un BigInt avec un Number. JavaScript gérera correctement la comparaison de leurs valeurs mathématiques.
console.log(10n > 5); // true
console.log(10n < 20); // true
console.log(100n >= 100); // true
console.log(99n <= 100); // true
Égalité (`==` vs. `===`)
La différence entre l'égalité simple (`==`) et l'égalité stricte (`===`) est très importante avec BigInt.
- L'égalité stricte (
===) vérifie à la fois la valeur et le type. CommeBigIntetNumbersont des types différents,10n === 10sera toujours faux. - L'égalité simple (
==) effectue une coercition de type. Elle considérera10n == 10comme vrai car leurs valeurs mathématiques sont les mêmes.
console.log(10n == 10); // true
console.log(10n === 10); // false (different types)
console.log(10n === 10n); // true (same value and type)
Pour plus de clarté et pour éviter un comportement inattendu, il est souvent préférable d'utiliser l'égalité stricte et de s'assurer que vous comparez des valeurs du même type.
Contexte booléen
Comme les Numbers, les BigInts peuvent être évalués dans un contexte booléen (par exemple, dans une instruction if). La valeur 0n est considérée comme "falsy" (fausse), tandis que toutes les autres valeurs BigInt (positives ou négatives) sont considérées comme "truthy" (vraies).
if (0n) {
// This code will not run
} else {
console.log("0n is falsy");
}
if (1n && -10n) {
console.log("Non-zero BigInts are truthy");
}
Cas d'utilisation pratiques de BigInt dans un contexte mondial
Maintenant que nous comprenons la mécanique, explorons où BigInt brille dans les applications réelles et internationales.
1. Technologie financière (FinTech)
L'arithmétique à virgule flottante est notoirement problématique pour les calculs financiers en raison des erreurs d'arrondi. Une pratique mondiale courante consiste à représenter les valeurs monétaires sous forme d'entiers de la plus petite unité monétaire (par exemple, les centimes pour l'USD, les yens pour le JPY, les satoshis pour le Bitcoin).
Bien que les Numbers standards puissent suffire pour des montants plus faibles, BigInt devient inestimable lorsqu'il s'agit de transactions importantes, de totaux agrégés ou de crypto-monnaies, qui impliquent souvent de très grands nombres.
// Representing a large transfer in the smallest unit (e.g., Wei for Ethereum)
const walletBalance = 1234567890123456789012345n; // A large amount of Wei
const transactionAmount = 9876543210987654321n;
const newBalance = walletBalance - transactionAmount;
console.log(`New balance: ${newBalance.toString()} Wei`);
// New balance: 1224691346912369134691246 Wei
L'utilisation de BigInt garantit que chaque unité est prise en compte, éliminant les erreurs d'arrondi qui pourraient survenir avec les mathématiques en virgule flottante.
2. Cryptographie
La cryptographie moderne, telle que l'algorithme RSA utilisé dans le chiffrement TLS/SSL sur le web, repose sur l'arithmétique avec des nombres premiers extrêmement grands. Ces nombres font souvent 2048 bits ou plus, dépassant de loin les capacités du type Number de JavaScript.
Avec BigInt, les algorithmes cryptographiques peuvent maintenant être implémentés ou polyfillés directement en JavaScript, ouvrant de nouvelles possibilités pour les outils de sécurité dans le navigateur et les applications basées sur WebAssembly.
3. Gestion des identifiants 64 bits
Comme mentionné précédemment, de nombreux systèmes distribués et bases de données génèrent des identifiants uniques de 64 bits. C'est un modèle courant dans les systèmes à grande échelle développés par des entreprises du monde entier.
Avant BigInt, les applications JavaScript qui consommaient des API renvoyant ces identifiants devaient les traiter comme des chaînes de caractères pour éviter la perte de précision. C'était une solution de contournement fastidieuse.
// An API response with a 64-bit user ID
const apiResponse = '{"userId": "1143534363363377152", "username": "dev_user"}';
// Old way (parsing as string)
const userDataString = JSON.parse(apiResponse);
console.log(userDataString.userId); // "1143534363363377152"
// Any math would require a library or string manipulation.
// New way (with a custom reviver and BigInt)
const userDataBigInt = JSON.parse(apiResponse, (key, value) => {
// A simple check to convert potential ID fields to BigInt
if (key === 'userId' && typeof value === 'string' && /^[0-9]+$/.test(value)) {
return BigInt(value);
}
return value;
});
console.log(userDataBigInt.userId); // 1143534363363377152n
console.log(typeof userDataBigInt.userId); // "bigint"
Avec BigInt, ces identifiants peuvent être représentés par leur type numérique approprié, permettant un tri, une comparaison et un stockage corrects.
4. Calcul scientifique et mathématique
Des domaines comme la théorie des nombres, la combinatoire et les simulations physiques nécessitent souvent des calculs qui produisent des entiers plus grands que Number.MAX_SAFE_INTEGER. Par exemple, le calcul de grandes factorielles ou de termes de la suite de Fibonacci peut être effectué facilement avec BigInt.
function factorial(n) {
// Use BigInts from the start
let result = 1n;
for (let i = 2n; i <= n; i++) {
result *= i;
}
return result;
}
// Calculate factorial of 50
const fact50 = factorial(50n);
console.log(fact50.toString());
// 30414093201713378043612608166064768844377641568960512000000000000n
Sujets avancés et pièges courants
Bien que BigInt soit puissant, il y a plusieurs nuances et problèmes potentiels à connaître.
Sérialisation JSON : un piège majeur
Un défi important se présente lorsque vous essayez de sérialiser un objet contenant un BigInt en une chaîne JSON. Par défaut, `JSON.stringify()` lèvera une `TypeError` lorsqu'il rencontrera un BigInt.
const data = {
id: 12345678901234567890n,
status: "active"
};
try {
JSON.stringify(data);
} catch (error) {
console.error(error); // TypeError: Do not know how to serialize a BigInt
}
C'est parce que la spécification JSON n'a pas de type de données pour les entiers arbitrairement grands, et une conversion silencieuse en un nombre standard pourrait entraîner une perte de précision. Pour gérer cela, vous devez fournir une stratégie de sérialisation personnalisée.
Solution 1 : Implémenter une méthode `toJSON`
Vous pouvez ajouter une méthode `toJSON` au `BigInt.prototype`. Cette méthode sera automatiquement appelée par `JSON.stringify()`.
// Add this to your application's setup file
BigInt.prototype.toJSON = function() {
return this.toString();
};
const data = { id: 12345678901234567890n, status: "active" };
const jsonString = JSON.stringify(data);
console.log(jsonString); // "{"id":"12345678901234567890","status":"active"}"
Solution 2 : Utiliser une fonction `replacer`
Si vous ne souhaitez pas modifier un prototype global, vous pouvez passer une fonction `replacer` à `JSON.stringify()`.
const replacer = (key, value) => {
if (typeof value === 'bigint') {
return value.toString();
}
return value;
};
const data = { id: 12345678901234567890n, status: "active" };
const jsonString = JSON.stringify(data, replacer);
console.log(jsonString); // "{"id":"12345678901234567890","status":"active"}"
N'oubliez pas que vous aurez également besoin d'une fonction `reviver` correspondante lors de l'utilisation de `JSON.parse()` pour reconvertir la représentation en chaîne de caractères en un BigInt, comme montré précédemment dans l'exemple des identifiants 64 bits.
Opérations bit à bit
BigInt prend également en charge les opérations bit à bit (&, |, ^, ~, <<, >>), qui traitent le BigInt comme une séquence de bits en représentation en complément à deux. C'est extrêmement utile pour la manipulation de données à bas niveau, l'analyse de protocoles binaires ou l'implémentation de certains algorithmes.
const mask = 0b1111n; // A 4-bit mask
const value = 255n; // 0b11111111n
// Bitwise AND
console.log(value & mask); // 15n (which is 0b1111n)
// Left shift
console.log(1n << 64n); // 18446744073709551616n (2^64)
Notez que l'opérateur de décalage à droite non signé (>>>) n'est pas pris en charge pour BigInt, car chaque BigInt est signé.
Considérations sur les performances
Bien que BigInt soit un outil puissant, il ne remplace pas directement Number. Les opérations sur les BigInts sont généralement plus lentes que leurs homologues Number car elles nécessitent une allocation de mémoire et une logique de calcul plus complexes et de longueur variable. Pour l'arithmétique standard qui se situe confortablement dans la plage des entiers sûrs, vous devriez continuer à utiliser le type Number pour des performances optimales.
La règle d'or est simple : Utilisez Number par défaut. Passez à BigInt uniquement lorsque vous savez que vous aurez affaire à des entiers susceptibles de dépasser Number.MAX_SAFE_INTEGER.
Support des navigateurs et des environnements
BigInt fait partie de la norme ES2020 et est largement pris en charge dans tous les navigateurs web modernes (Chrome, Firefox, Safari, Edge) et les environnements côté serveur comme Node.js (version 10.4.0 et ultérieures). Cependant, il n'est pas disponible dans les anciens navigateurs comme Internet Explorer. Si vous devez prendre en charge des environnements hérités, vous devrez toujours vous fier à des bibliothèques tierces pour les grands nombres et potentiellement utiliser un transpileur comme Babel, qui peut fournir un polyfill.
Pour un public mondial, il est toujours judicieux de consulter une ressource de compatibilité comme "Can I Use..." pour s'assurer que votre base d'utilisateurs cible peut exécuter votre code sans problème.
Conclusion : Une nouvelle frontière pour JavaScript
L'introduction de BigInt marque une maturation significative du langage JavaScript. Il résout directement une limitation de longue date et permet aux développeurs de créer une nouvelle classe d'applications nécessitant une arithmétique entière de haute précision. En fournissant une solution native et intégrée, BigInt élimine le besoin de bibliothèques externes pour de nombreux cas d'utilisation courants, ce qui conduit à un code plus propre, plus efficace et plus sécurisé.
Points clés à retenir pour les développeurs mondiaux :
- Utilisez BigInt pour les entiers au-delà de 253 - 1 : Chaque fois que votre application pourrait gérer des entiers plus grands que
Number.MAX_SAFE_INTEGER, utilisez BigInt pour garantir la précision. - Soyez explicite avec les types : Rappelez-vous que vous ne pouvez pas mélanger
BigIntetNumberdans les opérations arithmétiques. Effectuez toujours des conversions explicites et soyez attentif à la perte de précision potentielle lors de la conversion d'un grand BigInt en Number. - Maîtrisez la gestion JSON : Soyez prêt à gérer la
TypeErrordeJSON.stringify(). Implémentez une stratégie de sérialisation et de désérialisation robuste en utilisant une méthodetoJSONou une pairereplacer/reviver. - Choisissez le bon outil pour le travail : BigInt est uniquement pour les entiers. Pour l'arithmétique décimale à précision arbitraire, les bibliothèques comme `Decimal.js` restent le choix approprié. Utilisez
Numberpour tous les autres calculs non entiers ou sur de petits entiers afin de maintenir les performances.
En adoptant BigInt, la communauté JavaScript internationale peut désormais aborder en toute confiance les défis de la finance, de la science, de l'intégrité des données et de la cryptographie, repoussant les limites de ce qui est possible sur le web et au-delà.