En omfattende guide til JavaScripts BigInt-primitiv. Lær at håndtere beregninger med store tal, bevare præcision ud over Number.MAX_SAFE_INTEGER og anvende BigInt i globale applikationer som kryptografi og fintech.
JavaScript BigInt-aritmetik: Et dybdegående kig på beregninger med store tal og præcisionshåndtering
I mange år stod JavaScript-udviklere over for en tavs, men betydelig begrænsning: den manglende evne til at repræsentere meget store heltal indbygget og præcist. Alle tal i JavaScript blev traditionelt repræsenteret som IEEE 754 dobbeltpræcisions flydende kommatal, hvilket pålægger et loft for heltalpræcision. Når beregninger involverede tal, der var større end, hvad der sikkert kunne indeholdes, måtte udviklere ty til tredjepartsbiblioteker. Dette ændrede sig med introduktionen af BigInt i ECMAScript 2020 (ES11), en revolutionerende funktion, der bragte heltal med vilkårlig præcision ind i sprogets kerne.
Denne omfattende guide er designet til et globalt publikum af udviklere. Vi vil udforske de problemer, BigInt løser, hvordan man bruger det til præcis aritmetik, dets anvendelse i den virkelige verden inden for områder som kryptografi og finans, samt de almindelige faldgruber, man skal undgå. Uanset om du bygger en fintech-platform, en videnskabelig simulation eller interagerer med systemer, der bruger 64-bit identifikatorer, er forståelse af BigInt afgørende for moderne JavaScript-udvikling.
Glasloftet i JavaScripts `Number`-type
Før vi kan værdsætte løsningen, må vi først forstå problemet. JavaScripts standard Number-type har, selvom den er alsidig, en fundamental begrænsning, når det kommer til heltalpræcision. Dette er ikke en fejl; det er en direkte konsekvens af dens design baseret på IEEE 754-standarden for flydende komma-aritmetik.
Forståelse af `Number.MAX_SAFE_INTEGER`
Number-typen kan kun sikkert repræsentere heltal op til en vis værdi. Denne tærskel er eksponeret som en statisk egenskab: Number.MAX_SAFE_INTEGER.
Dens værdi er 9.007.199.254.740.991, eller 253 - 1. Hvorfor netop dette tal? I de 64 bit, der bruges til en dobbeltpræcisions float, er 52 bit dedikeret til mantissen (de betydende cifre), én bit til fortegnet og 11 bit til eksponenten. Denne struktur tillader et meget stort interval af værdier, men begrænser den sammenhængende, gab-løse repræsentation af heltal.
Lad os se, hvad der sker, når vi forsøger at overskride denne grænse:
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 - Hovsa!
console.log(oneMore === twoMore); // true
Som du kan se, mister talsystemet sin evne til at repræsentere hvert efterfølgende heltal, når vi krydser tærsklen. maxSafeInt + 1 og maxSafeInt + 2 evalueres til den samme værdi. Dette tavse tab af præcision kan føre til katastrofale fejl i applikationer, der afhænger af nøjagtig heltal-aritmetik, såsom finansielle beregninger eller håndtering af store database-ID'er.
Hvornår er dette relevant?
Denne begrænsning er ikke kun en teoretisk kuriositet. Den har betydelige konsekvenser i den virkelige verden:
- Database-ID'er: Mange moderne databasesystemer, som f.eks. PostgreSQL, bruger en 64-bit heltalstype (
BIGINT) til primære nøgler. Disse ID'er kan let overstigeNumber.MAX_SAFE_INTEGER. Når en JavaScript-klient henter dette ID, kan det blive afrundet forkert, hvilket fører til datakorruption eller manglende evne til at hente den korrekte post. - API-integrationer: Tjenester som Twitter (nu X) bruger 64-bit heltal kaldet "Snowflakes" til tweet-ID'er. Korrekt håndtering af disse ID'er i en JavaScript-frontend kræver særlig omhu.
- Kryptografi: Kryptografiske operationer involverer ofte aritmetik med ekstremt store primtal, langt ud over kapaciteten af den almindelige
Number-type. - Tidsstempler med høj præcision: Nogle systemer leverer tidsstempler med nanosekundpræcision, ofte repræsenteret som et 64-bit heltalsantal fra en epoke. At gemme dette i et standard
Numberville afkorte dets præcision.
Introduktion til BigInt: Løsningen for heltal med vilkårlig præcision
BigInt blev introduceret specifikt for at løse dette problem. Det er en separat numerisk primitiv type i JavaScript, der kan repræsentere heltal med vilkårlig præcision. Dette betyder, at en BigInt ikke er begrænset af et fast antal bit; den kan vokse eller skrumpe for at rumme den værdi, den indeholder, kun begrænset af den tilgængelige hukommelse i værtssystemet.
Oprettelse af en BigInt
Der er to primære måder at oprette en BigInt-værdi på:
- Tilføjelse af `n` til et heltalsliteral: Dette er den enkleste og mest almindelige metode.
- Brug af `BigInt()`-konstruktørfunktionen: Dette er nyttigt til at konvertere strenge eller Numbers til BigInts.
Her er nogle eksempler:
// Ved at bruge 'n'-suffikset
const aLargeNumber = 9007199254740991n;
const anEvenLargerNumber = 1234567890123456789012345678901234567890n;
// Ved at bruge BigInt()-konstruktøren
const fromString = BigInt("98765432109876543210");
const fromNumber = BigInt(100); // Opretter 100n
// Lad os verificere deres type
console.log(typeof aLargeNumber); // "bigint"
console.log(typeof fromString); // "bigint"
Vigtig bemærkning: Du kan ikke bruge `new`-operatoren med `BigInt()`, da det er en primitiv type, ikke et objekt. `new BigInt()` vil kaste en `TypeError`.
Grundlæggende aritmetik med BigInt
BigInt understøtter de standard aritmetiske operatorer, du kender, men de opfører sig strengt inden for heltallenes domæne.
Addition, subtraktion og multiplikation
Disse operatorer fungerer præcis, som du ville forvente, men med evnen til at håndtere enorme tal uden at miste præcision.
const num1 = 12345678901234567890n;
const num2 = 98765432109876543210n;
// Addition
console.log(num1 + num2); // 111111111011111111100n
// Subtraktion
console.log(num2 - num1); // 86419753208641975320n
// Multiplikation
console.log(num1 * 2n); // 24691357802469135780n
Division (`/`)
Her adskiller BigInts adfærd sig markant fra standard Number-division. Fordi BigInts kun kan repræsentere hele tal, bliver resultatet af en division altid afkortet mod nul (brøkdelen kasseres).
const dividend = 10n;
const divisor = 3n;
console.log(dividend / divisor); // 3n (ikke 3.333...)
const negativeDividend = -10n;
console.log(negativeDividend / divisor); // -3n
// Til sammenligning med Number-division
console.log(10 / 3); // 3.3333333333333335
Denne heltal-eneste division er afgørende. Hvis du har brug for at udføre beregninger, der kræver decimalpræcision, er BigInt ikke det rette værktøj. Du bliver nødt til at bruge biblioteker som `Decimal.js` eller administrere decimaldelen manuelt (for eksempel ved at arbejde med den mindste valutaenhed i finansielle beregninger).
Rest (`%`) og potensopløftning (`**`)
Rest-operatoren (`%`) og potensopløftningsoperatoren (`**`) fungerer også som forventet med BigInt-værdier.
console.log(10n % 3n); // 1n
console.log(-10n % 3n); // -1n
// Potensopløftning kan skabe virkelig massive tal
const base = 2n;
const exponent = 100n;
const hugeNumber = base ** exponent;
console.log(hugeNumber); // 1267650600228229401496703205376n
Den strenge regel: Ingen blanding af `BigInt` og `Number`
En af de vigtigste regler at huske, når man arbejder med BigInt, er, at du ikke kan blande BigInt- og Number-operander i de fleste aritmetiske operationer. Forsøg på at gøre det vil resultere i en `TypeError`.
Dette designvalg var bevidst. Det forhindrer udviklere i ved et uheld at miste præcision, når en BigInt implicit bliver tvunget til et Number. Sproget tvinger dig til at være eksplicit om dine intentioner.
const myBigInt = 100n;
const myNumber = 50;
try {
const result = myBigInt + myNumber; // Dette vil fejle
} catch (error) {
console.error(error); // TypeError: Kan ikke blande BigInt og andre typer, brug eksplicitte konverteringer
}
Den korrekte tilgang: Eksplicit konvertering
For at udføre en operation mellem en BigInt og et Number skal du eksplicit konvertere den ene til den andens type.
const myBigInt = 100n;
const myNumber = 50;
// Konverter Number til en BigInt
const result1 = myBigInt + BigInt(myNumber);
console.log(result1); // 150n
// Konverter BigInt til et Number (brug med forsigtighed!)
const result2 = Number(myBigInt) + myNumber;
console.log(result2); // 150
Advarsel: At konvertere en BigInt til et Number med `Number()` er farligt, hvis BigInt-værdien er uden for det sikre heltalsområde. Dette kan genintroducere netop de præcisionsfejl, som BigInt er designet til at forhindre.
const veryLargeBigInt = 9007199254740993n;
const convertedToNumber = Number(veryLargeBigInt);
console.log(veryLargeBigInt); // 9007199254740993n
console.log(convertedToNumber); // 9007199254740992 - Præcision tabt!
Den generelle regel er: Hvis du arbejder med potentielt store heltal, skal du forblive inden for BigInt-økosystemet for alle dine beregninger. Konverter kun tilbage til et Number, hvis du er sikker på, at værdien er inden for det sikre interval.
Sammenlignings- og logiske operatorer
Mens aritmetiske operatorer er strenge med hensyn til typeblanding, er sammenlignings- og logiske operatorer mere eftergivende.
Relationelle sammenligninger (`>`, `<`, `>=`, `<=`)
Du kan trygt sammenligne en BigInt med et Number. JavaScript vil håndtere sammenligningen af deres matematiske værdier korrekt.
console.log(10n > 5); // true
console.log(10n < 20); // true
console.log(100n >= 100); // true
console.log(99n <= 100); // true
Ligestilling (`==` vs. `===`)
Forskellen mellem løs lighed (`==`) og streng lighed (`===`) er meget vigtig med BigInt.
- Streng lighed (`===`) kontrollerer både værdi og type. Da `BigInt` og `Number` er forskellige typer, vil `10n === 10` altid være false.
- Løs lighed (`==`) udfører typekonvertering. Den vil betragte `10n == 10` som true, fordi deres matematiske værdier er de samme.
console.log(10n == 10); // true
console.log(10n === 10); // false (forskellige typer)
console.log(10n === 10n); // true (samme værdi og type)
For klarhedens skyld og for at undgå uventet adfærd er det ofte bedste praksis at bruge streng lighed og sikre, at du sammenligner værdier af samme type.
Boolesk kontekst
Ligesom Numbers kan BigInts evalueres i en boolesk kontekst (f.eks. i en `if`-sætning). Værdien `0n` betragtes som falsy, mens alle andre BigInt-værdier (positive eller negative) betragtes som truthy.
if (0n) {
// Denne kode vil ikke køre
} else {
console.log("0n er falsy");
}
if (1n && -10n) {
console.log("Ikke-nul BigInts er truthy");
}
Praktiske anvendelsestilfælde for BigInt i en global kontekst
Nu hvor vi forstår mekanikken, lad os udforske, hvor BigInt brillerer i virkelige, internationale applikationer.
1. Finansiel teknologi (FinTech)
Flydende komma-aritmetik er notorisk problematisk for finansielle beregninger på grund af afrundingsfejl. En almindelig global praksis er at repræsentere monetære værdier som heltal af den mindste valutaenhed (f.eks. cent for USD, yen for JPY, satoshis for Bitcoin).
Mens standard Numbers kan være tilstrækkelige til mindre beløb, bliver BigInt uvurderlig, når man håndterer store transaktioner, samlede totaler eller kryptovalutaer, som ofte involverer meget store tal.
// Repræsenterer en stor overførsel i den mindste enhed (f.eks. Wei for Ethereum)
const walletBalance = 1234567890123456789012345n; // Et stort beløb i Wei
const transactionAmount = 9876543210987654321n;
const newBalance = walletBalance - transactionAmount;
console.log(`Ny saldo: ${newBalance.toString()} Wei`);
// Ny saldo: 1224691346912369134691246 Wei
Brug af BigInt sikrer, at hver eneste enhed bliver talt med, hvilket eliminerer de afrundingsfejl, der kunne opstå med flydende komma-matematik.
2. Kryptografi
Moderne kryptografi, såsom RSA-algoritmen, der bruges i TLS/SSL-kryptering på tværs af nettet, er afhængig af aritmetik med ekstremt store primtal. Disse tal er ofte 2048 bit eller større, hvilket langt overstiger kapaciteten af JavaScripts Number-type.
Med BigInt kan kryptografiske algoritmer nu implementeres eller polyfilles direkte i JavaScript, hvilket åbner nye muligheder for sikkerhedsværktøjer i browseren og WebAssembly-drevne applikationer.
3. Håndtering af 64-bit identifikatorer
Som nævnt tidligere genererer mange distribuerede systemer og databaser 64-bit unikke identifikatorer. Dette er et almindeligt mønster i stor-skala systemer udviklet af virksomheder verden over.
Før BigInt måtte JavaScript-applikationer, der forbrugte API'er, som returnerede disse ID'er, behandle dem som strenge for at undgå præcisionstab. Dette var en besværlig løsning.
// Et API-svar med et 64-bit bruger-ID
const apiResponse = '{"userId": "1143534363363377152", "username": "dev_user"}';
// Gammel metode (parser som streng)
const userDataString = JSON.parse(apiResponse);
console.log(userDataString.userId); // "1143534363363377152"
// Enhver matematik ville kræve et bibliotek eller strengmanipulation.
// Ny metode (med en brugerdefineret reviver og BigInt)
const userDataBigInt = JSON.parse(apiResponse, (key, value) => {
// En simpel kontrol for at konvertere potentielle ID-felter til 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"
Med BigInt kan disse ID'er repræsenteres som deres korrekte numeriske type, hvilket tillader korrekt sortering, sammenligning og lagring.
4. Videnskabelig og matematisk databehandling
Felter som talteori, kombinatorik og fysiksimulationer kræver ofte beregninger, der producerer heltal større end Number.MAX_SAFE_INTEGER. For eksempel kan beregning af store fakulteter eller led i Fibonacci-sekvensen let gøres med BigInt.
function factorial(n) {
// Brug BigInts fra starten
let result = 1n;
for (let i = 2n; i <= n; i++) {
result *= i;
}
return result;
}
// Beregn fakultetet af 50
const fact50 = factorial(50n);
console.log(fact50.toString());
// 30414093201713378043612608166064768844377641568960512000000000000n
Avancerede emner og almindelige faldgruber
Selvom BigInt er et kraftfuldt værktøj, er der flere nuancer og potentielle problemer, man skal være opmærksom på.
JSON-serialisering: En stor faldgrube
En betydelig udfordring opstår, når du forsøger at serialisere et objekt, der indeholder en BigInt, til en JSON-streng. Som standard vil `JSON.stringify()` kaste en `TypeError`, når den støder på en BigInt.
const data = {
id: 12345678901234567890n,
status: "active"
};
try {
JSON.stringify(data);
} catch (error) {
console.error(error); // TypeError: Ved ikke, hvordan en BigInt serialiseres
}
Dette skyldes, at JSON-specifikationen ikke har en datatype for vilkårligt store heltal, og en tavs konvertering til et standardnummer kunne føre til præcisionstab. For at håndtere dette skal du levere en brugerdefineret serialiseringsstrategi.
Løsning 1: Implementér en `toJSON`-metode
Du kan tilføje en `toJSON`-metode til `BigInt.prototype`. Denne metode vil automatisk blive kaldt af `JSON.stringify()`.
// Føj dette til din applikations opsætningsfil
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"}"
Løsning 2: Brug en `replacer`-funktion
Hvis du ikke vil ændre en global prototype, kan du sende en `replacer`-funktion til `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"}"
Husk, at du også skal have en tilsvarende `reviver`-funktion, når du bruger `JSON.parse()` for at konvertere strengrepræsentationen tilbage til en BigInt, som vist i 64-bit ID-eksemplet tidligere.
Bitvise operationer
BigInt understøtter også bitvise operationer (`&`, `|`, `^`, `~`, `<<`, `>>`), som behandler BigInt som en sekvens af bits i to's komplement-repræsentation. Dette er ekstremt nyttigt til lav-niveau datamanipulation, parsing af binære protokoller eller implementering af visse algoritmer.
const mask = 0b1111n; // En 4-bit maske
const value = 255n; // 0b11111111n
// Bitvis AND
console.log(value & mask); // 15n (som er 0b1111n)
// Venstreskift
console.log(1n << 64n); // 18446744073709551616n (2^64)
Bemærk, at den usignerede højre-skift-operator (`>>>`) ikke understøttes for BigInt, da enhver BigInt er signeret.
Overvejelser om ydeevne
Selvom BigInt er et kraftfuldt værktøj, er det ikke en direkte erstatning for Number. Operationer på BigInts er generelt langsommere end deres Number-modparter, fordi de kræver mere kompleks, variabel-længde hukommelsesallokering og beregningslogik. For standard aritmetik, der komfortabelt falder inden for det sikre heltalsområde, bør du fortsat bruge Number-typen for optimal ydeevne.
Tommelfingerreglen er simpel: Brug Number som standard. Skift kun til BigInt, når du ved, at du skal håndtere heltal, der kan overstige `Number.MAX_SAFE_INTEGER`.
Browser- og miljøunderstøttelse
BigInt er en del af ES2020-standarden og er bredt understøttet i alle moderne webbrowsere (Chrome, Firefox, Safari, Edge) og server-side miljøer som Node.js (version 10.4.0 og senere). Det er dog ikke tilgængeligt i ældre browsere som Internet Explorer. Hvis du har brug for at understøtte ældre miljøer, skal du stadig stole på tredjeparts biblioteker til store tal og potentielt bruge en transpiler som Babel, der kan levere en polyfill.
For et globalt publikum er det altid klogt at tjekke en kompatibilitetsressource som "Can I Use..." for at sikre, at din målbrugerbase kan køre din kode uden problemer.
Konklusion: En ny grænse for JavaScript
Introduktionen af BigInt markerer en betydelig modning af JavaScript-sproget. Det adresserer direkte en langvarig begrænsning og giver udviklere mulighed for at bygge en ny klasse af applikationer, der kræver højpræcisions heltal-aritmetik. Ved at levere en indbygget, nativ løsning eliminerer BigInt behovet for eksterne biblioteker til mange almindelige brugsscenarier, hvilket fører til renere, mere effektiv og mere sikker kode.
Vigtige pointer for globale udviklere:
- Brug BigInt for heltal ud over 253 - 1: Når din applikation kan håndtere heltal større end `Number.MAX_SAFE_INTEGER`, brug BigInt for at garantere præcision.
- Vær eksplicit med typer: Husk, at du ikke kan blande `BigInt` og `Number` i aritmetiske operationer. Udfør altid eksplicitte konverteringer og vær opmærksom på potentielt præcisionstab, når du konverterer en stor BigInt tilbage til et Number.
- Mestrér JSON-håndtering: Vær forberedt på at håndtere `TypeError` fra `JSON.stringify()`. Implementer en robust serialiserings- og deserialiseringsstrategi ved hjælp af en `toJSON`-metode eller et `replacer`/`reviver`-par.
- Vælg det rigtige værktøj til opgaven: BigInt er kun til heltal. For vilkårlig-præcision decimal-aritmetik er biblioteker som `Decimal.js` stadig det passende valg. Brug `Number` til alle andre ikke-heltal eller små heltalsberegninger for at opretholde ydeevnen.
Ved at omfavne BigInt kan det internationale JavaScript-fællesskab nu trygt tackle udfordringer inden for finans, videnskab, dataintegritet og kryptografi og skubbe grænserne for, hvad der er muligt på nettet og videre.