Maîtrisez BigInt en JavaScript pour le calcul précis de grands entiers. Explorez la syntaxe, les cas d'usage en cryptographie et finance, et surmontez les pièges comme la sérialisation JSON.
JavaScript BigInt : Guide Complet sur le Calcul avec de Grands Nombres
Pendant de nombreuses années, les développeurs JavaScript ont été confrontés à une limitation silencieuse mais significative : la capacité native du langage à gérer les nombres. Bien que parfaitement adapté aux calculs quotidiens, le type Number
de JavaScript échouait face aux entiers véritablement massifs requis dans des domaines tels que la cryptographie, le calcul scientifique et les systèmes de données modernes. Cela a conduit à un monde de solutions de contournement, de bibliothèques tierces et d'erreurs de précision subtiles et difficiles à déboguer.
Cette ère est révolue. L'introduction de BigInt en tant que type primitif natif de JavaScript a révolutionné notre façon de travailler avec les grands nombres. Il offre un moyen robuste, ergonomique et efficace d'effectuer des calculs arithmétiques sur des entiers de précision arbitraire, directement dans le langage.
Ce guide complet s'adresse aux développeurs du monde entier. Nous allons explorer en profondeur le "pourquoi, quoi et comment" de BigInt. Que vous développiez une application financière, interagissiez avec une blockchain ou cherchiez simplement à comprendre pourquoi votre grand identifiant unique provenant d'une API se comporte étrangement, cet article vous donnera les connaissances nécessaires pour maîtriser BigInt.
Le Problème : Les Limites du Type Number de JavaScript
Avant de pouvoir apprécier la solution, nous devons bien comprendre le problème. Pendant la majeure partie de son histoire, JavaScript n'a eu qu'un seul type de nombre : le type Number
. En coulisses, il est représenté comme un nombre à virgule flottante double précision 64 bits selon la norme IEEE 754. Ce format est excellent pour représenter une large gamme de valeurs, y compris les décimales, mais il présente une limitation critique en ce qui concerne les entiers.
Découvrez MAX_SAFE_INTEGER
En raison de sa représentation en virgule flottante, il existe une limite à la taille d'un entier pouvant être représenté avec une précision parfaite. Cette limite est exposée via une constante : Number.MAX_SAFE_INTEGER
.
Sa valeur est de 253 - 1, soit 9 007 199 254 740 991. Appelons-le neuf millions de milliards pour faire court.
Tout entier dans l'intervalle de -Number.MAX_SAFE_INTEGER
Ă +Number.MAX_SAFE_INTEGER
est considéré comme un "entier sûr". Cela signifie qu'il peut être représenté exactement et comparé correctement. Mais que se passe-t-il lorsque nous sortons de cet intervalle ?
Voyons cela en action :
const maxSafe = Number.MAX_SAFE_INTEGER;
console.log(maxSafe); // 9007199254740991
// Ajoutons 1
console.log(maxSafe + 1); // 9007199254740992 - Cela semble correct
// Ajoutons encore 1
console.log(maxSafe + 2); // 9007199254740992 - Oups. Résultat incorrect.
// Ça empire
console.log(maxSafe + 3); // 9007199254740994 - Attendez, quoi ?
console.log(maxSafe + 4); // 9007199254740996 - Il saute des nombres !
// La vérification de l'égalité échoue également
console.log(maxSafe + 1 === maxSafe + 2); // true - C'est mathématiquement faux !
Comme vous pouvez le voir, une fois que nous dépassons Number.MAX_SAFE_INTEGER
, JavaScript ne peut plus garantir la précision de nos calculs. La représentation des nombres commence à avoir des lacunes, ce qui entraîne des erreurs d'arrondi et des résultats incorrects. C'est un cauchemar pour les applications qui exigent de la précision avec de grands entiers.
Les Anciennes Solutions de Contournement
Pendant des années, la communauté mondiale des développeurs s'est appuyée sur des bibliothèques externes pour résoudre ce problème. Des bibliothèques comme bignumber.js
, decimal.js
et long.js
sont devenues des outils standards. Elles fonctionnaient en représentant les grands nombres sous forme de chaînes de caractères ou de tableaux de chiffres et en implémentant les opérations arithmétiques de manière logicielle.
Bien qu'efficaces, ces bibliothèques présentaient des inconvénients :
- Surcharge de Performance : Les opérations étaient nettement plus lentes que les calculs avec des nombres natifs.
- Taille du Bundle : Elles alourdissaient les bundles des applications, une préoccupation pour la performance web.
- Syntaxe Différente : Les développeurs devaient utiliser des méthodes d'objet (ex:
a.add(b)
) au lieu des opérateurs arithmétiques standards (a + b
), rendant le code moins intuitif.
Introduction Ă BigInt : La Solution Native
BigInt a été introduit dans ES2020 pour résoudre ce problème de manière native. Un BigInt
est un nouveau type primitif en JavaScript qui offre un moyen de représenter des nombres entiers supérieurs à 253 - 1.
La caractéristique clé de BigInt est que sa taille n'est pas fixe. Il peut représenter des entiers arbitrairement grands, limité uniquement par la mémoire disponible dans le système hôte. Cela élimine complètement les problèmes de précision que nous avons vus avec le type Number
.
Comment Créer un BigInt
Il y a deux manières principales de créer un 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 lors de la conversion d'une valeur d'un autre type, comme une chaîne de caractères ou un nombre.
Voici Ă quoi cela ressemble en code :
// 1. Utiliser le suffixe 'n'
const myFirstBigInt = 900719925474099199n;
const anotherBigInt = 123456789012345678901234567890n;
// 2. Utiliser le constructeur BigInt()
const fromString = BigInt("98765432109876543210");
const fromNumber = BigInt(100);
// Vous pouvez vérifier le type
console.log(typeof myFirstBigInt); // "bigint"
console.log(typeof 100); // "number"
Avec BigInt, notre calcul précédent qui échouait fonctionne maintenant parfaitement :
const maxSafePlusOne = BigInt(Number.MAX_SAFE_INTEGER) + 1n;
const maxSafePlusTwo = BigInt(Number.MAX_SAFE_INTEGER) + 2n;
console.log(maxSafePlusOne.toString()); // "9007199254740992"
console.log(maxSafePlusTwo.toString()); // "9007199254740993"
// L'égalité fonctionne comme prévu
console.log(maxSafePlusOne === maxSafePlusTwo); // false
Travailler avec BigInt : Syntaxe et Opérations
Les BigInts se comportent un peu comme des nombres normaux, mais avec quelques différences cruciales que chaque développeur doit comprendre pour éviter les bugs.
Opérations Arithmétiques
Tous les opérateurs arithmétiques standards fonctionnent avec les BigInts :
- Addition :
+
- Soustraction :
-
- Multiplication :
*
- Exponentiation :
**
- Modulo (Reste) :
%
Le seul opérateur qui se comporte différemment est la division (`/`).
const a = 10n;
const b = 3n;
console.log(a + b); // 13n
console.log(a - b); // 7n
console.log(a * b); // 30n
console.log(a ** b); // 1000n
console.log(a % b); // 1n
La Subtilité de la Division
Puisque les BigInts ne peuvent représenter que des nombres entiers, le résultat d'une division est toujours tronqué (la partie fractionnaire est supprimée). Il n'y a pas d'arrondi.
const a = 10n;
const b = 3n;
console.log(a / b); // 3n (pas 3.333...n)
const c = 9n;
const d = 10n;
console.log(c / d); // 0n
C'est une distinction essentielle. Si vous avez besoin d'effectuer des calculs avec des décimales, BigInt n'est pas le bon outil. Vous devriez continuer à utiliser Number
ou une bibliothèque dédiée aux nombres décimaux.
Comparaison et Égalité
Les opérateurs de comparaison comme >
, <
, >=
, et <=
fonctionnent de manière transparente entre les BigInts, et même entre un BigInt et un Number.
console.log(10n > 5); // true
console.log(10n < 20); // true
console.log(10n > 20n); // false
Cependant, l'égalité est plus nuancée et est une source fréquente de confusion.
- Égalité large (`==`) : Cet opérateur effectue une coercition de type. Il considère qu'un BigInt et un Number ayant la même valeur mathématique sont égaux.
- Égalité stricte (`===`) : Cet opérateur n'effectue pas de coercition de type. Puisque BigInt et Number sont des types différents, il retournera toujours
false
en les comparant.
console.log(10n == 10); // true - Soyez prudent avec ça !
console.log(10n === 10); // false - Recommandé pour plus de clarté.
console.log(0n == 0); // true
console.log(0n === 0); // false
Bonne Pratique : Pour éviter les bugs subtils, utilisez toujours l'égalité stricte (`===`) et soyez explicite sur les types que vous comparez. Si vous devez comparer un BigInt et un Number, il est souvent plus clair de convertir l'un en l'autre d'abord, en gardant à l'esprit la perte potentielle de précision.
L'Incompatibilité des Types : Une Séparation Stricte
JavaScript applique une règle stricte : 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
. C'est un choix de conception délibéré pour empêcher les développeurs de perdre accidentellement de la précision.
const myBigInt = 100n;
const myNumber = 50;
try {
const result = myBigInt + myNumber; // Ceci lèvera une erreur
} catch (error) {
console.log(error); // TypeError: Cannot mix BigInt and other types, use explicit conversions
}
La Bonne Approche : Conversion Explicite
Pour effectuer une opération entre un BigInt et un Number, vous devez explicitement convertir l'un d'eux.
const myBigInt = 100n;
const myNumber = 50;
// Convertir Number en BigInt (sûr)
const result1 = myBigInt + BigInt(myNumber);
console.log(result1); // 150n
// Convertir BigInt en Number (potentiellement dangereux !)
const veryLargeBigInt = 900719925474099199n;
// Ceci entraînera une perte de précision !
const unsafeNumber = Number(veryLargeBigInt);
console.log(unsafeNumber); // 900719925474099200 - La valeur a été arrondie !
const safeResult = Number(100n) + myNumber;
console.log(safeResult); // 150
Règle Critique : Ne convertissez un BigInt en Number que si vous êtes absolument certain qu'il se situe dans la plage des entiers sûrs. Sinon, convertissez toujours le Number en BigInt pour maintenir la précision.
Cas d'Usage Pratiques pour BigInt dans un Contexte Global
Le besoin de BigInt n'est pas un problème académique abstrait. Il résout des défis du monde réel rencontrés par les développeurs dans divers domaines internationaux.
1. Horodatages de Haute Précision
La fonction `Date.now()` de JavaScript renvoie le nombre de millisecondes depuis l'époque Unix. Bien que suffisant pour de nombreuses applications web, ce n'est pas assez granulaire pour les systèmes à haute performance. De nombreux systèmes distribués, bases de données et frameworks de journalisation à travers le monde utilisent des horodatages à la nanoseconde pour ordonner précisément les événements. Ces horodatages sont souvent représentés par des entiers 64 bits, qui sont trop grands pour le type Number
.
// Un horodatage d'un système à haute résolution (ex: en nanosecondes)
const nanoTimestampStr = "1670000000123456789";
// Utiliser Number entraîne une perte de précision
const lostPrecision = Number(nanoTimestampStr);
console.log(lostPrecision); // 1670000000123456800 - Incorrect !
// Utiliser BigInt préserve parfaitement la valeur
const correctTimestamp = BigInt(nanoTimestampStr);
console.log(correctTimestamp.toString()); // "1670000000123456789"
// Nous pouvons maintenant effectuer des calculs précis
const oneSecondInNanos = 1_000_000_000n;
const nextSecond = correctTimestamp + oneSecondInNanos;
console.log(nextSecond.toString()); // "1670001000123456789"
2. Identifiants Uniques (ID) des API
Un scénario très courant est l'interaction avec des API qui utilisent des entiers 64 bits pour les identifiants d'objets uniques. C'est un modèle utilisé par les grandes plateformes mondiales comme Twitter (ID Snowflake) et de nombreux systèmes de bases de données (ex: le type BIGINT
en SQL).
Lorsque vous récupérez des données d'une telle API, l'analyseur JSON de votre navigateur ou de votre environnement Node.js pourrait essayer d'analyser ce grand ID comme un Number
, ce qui corrompt les données avant même que vous ayez la chance de travailler avec.
// Une réponse JSON typique d'une API
// Note : L'ID est un grand nombre, pas une chaîne.
const jsonResponse = '{"id": 1367874743838343168, "text": "Hello, world!"}';
// JSON.parse standard corrompra l'ID
const parsedData = JSON.parse(jsonResponse);
console.log(parsedData.id); // 1367874743838343200 - Mauvais ID !
// Solution : Assurez-vous que l'API envoie les grands ID sous forme de chaînes.
const safeJsonResponse = '{"id": "1367874743838343168", "text": "Hello, world!"}';
const safeParsedData = JSON.parse(safeJsonResponse);
const userId = BigInt(safeParsedData.id);
console.log(userId); // 1367874743838343168n - Correct !
C'est pourquoi il est largement accepté comme une bonne pratique pour les API du monde entier de sérialiser les grands identifiants entiers sous forme de chaînes de caractères dans les charges utiles JSON afin d'assurer la compatibilité avec tous les clients.
3. Cryptographie
La cryptographie moderne est basée sur des mathématiques impliquant des entiers extrêmement grands. Des algorithmes comme RSA reposent sur des opérations avec des nombres de centaines, voire de milliers de bits. BigInt permet d'effectuer ces calculs nativement en JavaScript, ce qui est essentiel pour les applications cryptographiques basées sur le web, telles que celles utilisant l'API Web Crypto ou implémentant des protocoles dans Node.js.
Bien qu'un exemple cryptographique complet soit complexe, nous pouvons voir une démonstration conceptuelle :
// Deux très grands nombres premiers (à des fins de démonstration uniquement)
const p = 1143400375533529n;
const q = 982451653n; // Un plus petit pour l'exemple
// En RSA, on les multiplie pour obtenir le module
const n = p * q;
console.log(n.toString()); // "1123281328905333100311297"
// Ce calcul serait impossible avec le type Number.
// BigInt le gère sans effort.
4. Applications Financières et Blockchain
Lorsqu'on traite de la finance, en particulier dans le contexte des cryptomonnaies, la précision est primordiale. De nombreuses cryptomonnaies, comme le Bitcoin, mesurent la valeur dans leur plus petite unité (ex: les satoshis). L'offre totale de ces unités peut facilement dépasser Number.MAX_SAFE_INTEGER
. BigInt est l'outil parfait pour gérer ces grandes quantités précises sans recourir à l'arithmétique à virgule flottante, qui est sujette aux erreurs d'arrondi.
// 1 Bitcoin = 100 000 000 satoshis
const satoshisPerBTC = 100_000_000n;
// L'offre totale de Bitcoin est de 21 millions
const totalBTCSupply = 21_000_000n;
// Calcul du total de satoshis
const totalSatoshis = totalBTCSupply * satoshisPerBTC;
// 2 100 000 000 000 000 - C'est 2.1 millions de milliards
console.log(totalSatoshis.toString());
// Cette valeur est plus grande que Number.MAX_SAFE_INTEGER
console.log(totalSatoshis > BigInt(Number.MAX_SAFE_INTEGER)); // true
Sujets Avancés et Pièges Courants
Sérialisation et JSON.stringify()
L'un des problèmes les plus courants auxquels les développeurs sont confrontés est la sérialisation d'objets contenant des BigInts. Par défaut, JSON.stringify()
ne sait pas comment gérer le type bigint
et lèvera une TypeError
.
const data = {
id: 12345678901234567890n,
user: 'alex'
};
try {
JSON.stringify(data);
} catch (error) {
console.log(error); // TypeError: Do not know how to serialize a BigInt
}
Solution 1 : Implémenter une méthode `toJSON`
Vous pouvez indiquer à `JSON.stringify` comment gérer les BigInts en ajoutant une méthode `toJSON` au `BigInt.prototype`. Cette approche modifie le prototype global, ce qui peut être indésirable dans certains environnements partagés, mais elle est très efficace.
// Un patch global. À utiliser avec précaution.
BigInt.prototype.toJSON = function() {
return this.toString();
};
const data = { id: 12345678901234567890n, user: 'alex' };
const jsonString = JSON.stringify(data);
console.log(jsonString); // '{"id":"12345678901234567890","user":"alex"}'
Solution 2 : Utiliser une fonction `replacer`
Une approche plus sûre et plus localisée consiste à utiliser l'argument `replacer` de `JSON.stringify`. Cette fonction est appelée pour chaque paire clé/valeur et vous permet de transformer la valeur avant la sérialisation.
const data = { id: 12345678901234567890n, user: 'alex' };
const replacer = (key, value) => {
if (typeof value === 'bigint') {
return value.toString();
}
return value;
};
const jsonString = JSON.stringify(data, replacer);
console.log(jsonString); // '{"id":"12345678901234567890","user":"alex"}'
Opérations au Niveau du Bit (Bitwise)
BigInt supporte tous les opérateurs au niveau du bit que vous connaissez du type Number
: &
(ET), |
(OU), ^
(OU exclusif), ~
(NON), <<
(décalage à gauche), et >>
(décalage à droite avec propagation du signe). Ils sont particulièrement utiles pour travailler avec des formats de données de bas niveau, des permissions ou certains types d'algorithmes.
const permissions = 5n; // 0101 en binaire
const READ_PERMISSION = 4n; // 0100
const WRITE_PERMISSION = 2n; // 0010
// Vérifier si la permission de lecture est définie
console.log((permissions & READ_PERMISSION) > 0n); // true
// Vérifier si la permission d'écriture est définie
console.log((permissions & WRITE_PERMISSION) > 0n); // false
// Ajouter la permission d'écriture
const newPermissions = permissions | WRITE_PERMISSION;
console.log(newPermissions); // 7n (qui est 0111)
Considérations sur les Performances
Bien que BigInt soit incroyablement puissant, il est important de comprendre ses caractéristiques de performance :
- Number vs. BigInt : Pour les entiers dans la plage sûre, les opérations avec
Number
standard sont nettement plus rapides. C'est parce qu'elles peuvent souvent être mappées directement sur des instructions au niveau machine traitées par le CPU de l'ordinateur. Les opérations BigInt, étant de taille arbitraire, nécessitent des algorithmes logiciels plus complexes. - BigInt vs. Bibliothèques : Le
BigInt
natif est généralement beaucoup plus rapide que les bibliothèques de grands nombres basées sur JavaScript. L'implémentation fait partie du moteur JavaScript (comme V8 ou SpiderMonkey) et est écrite dans un langage de plus bas niveau comme le C++, ce qui lui confère un avantage de performance significatif.
La Règle d'Or : Utilisez Number
pour tous les calculs numériques, sauf si vous avez une raison spécifique de croire que les valeurs pourraient dépasser Number.MAX_SAFE_INTEGER
. Utilisez BigInt
lorsque vous avez besoin de ses capacités, pas comme un remplacement par défaut pour tous les nombres.
Compatibilité des Navigateurs et des Environnements
BigInt est une fonctionnalité JavaScript moderne, mais son support est maintenant largement répandu dans l'écosystème mondial.
- Navigateurs Web : Supporté dans tous les principaux navigateurs modernes (Chrome 67+, Firefox 68+, Safari 14+, Edge 79+).
- Node.js : Supporté depuis la version 10.4.0.
Pour les projets qui doivent prendre en charge de très anciens environnements, la transpilation avec des outils comme Babel peut être une option, mais cela s'accompagne d'une pénalité de performance. Compte tenu du large support aujourd'hui, la plupart des nouveaux projets peuvent utiliser BigInt nativement sans souci.
Conclusion et Bonnes Pratiques
BigInt est un ajout puissant et essentiel au langage JavaScript. Il fournit une solution native, efficace et ergonomique au problème de longue date de l'arithmétique des grands entiers, permettant de créer une nouvelle classe d'applications avec JavaScript, de la cryptographie à la gestion de données de haute précision.
Pour l'utiliser efficacement et éviter les pièges courants, gardez ces bonnes pratiques à l'esprit :
- Utilisez le Suffixe `n` : Préférez la syntaxe littérale
123n
pour créer des BigInts. C'est clair, concis et évite une perte de précision potentielle lors de la création. - Ne Mélangez Pas les Types : Rappelez-vous que vous ne pouvez pas mélanger BigInt et Number dans les opérations arithmétiques. Soyez explicite dans vos conversions :
BigInt()
ouNumber()
. - Priorisez la Précision : Lors de la conversion entre les types, privilégiez toujours la conversion d'un
Number
enBigInt
pour éviter toute perte de précision accidentelle. - Utilisez l'Égalité Stricte : Utilisez
===
au lieu de==
pour les comparaisons afin d'éviter les comportements déroutants causés par la coercition de type. - Gérez la Sérialisation JSON : Prévoyez la sérialisation des BigInts. Utilisez une fonction `replacer` personnalisée dans
JSON.stringify
pour une solution sûre et non globale. - Choisissez le Bon Outil : Utilisez
Number
pour les mathĂ©matiques gĂ©nĂ©rales dans la plage des entiers sĂ»rs pour de meilleures performances. Ne recourez ĂBigInt
que lorsque vous avez vraiment besoin de ses capacités de précision arbitraire.
En adoptant BigInt et en comprenant ses règles, vous pouvez écrire des applications JavaScript plus robustes, précises et puissantes, capables de relever des défis numériques de toute envergure.