Mestre JavaScripts BigInt for presis beregning med store heltall. Utforsk syntaks, bruksområder i kryptografi og finans, og unngå vanlige fallgruver som JSON-serialisering.
JavaScript BigInt: En Omfattende Guide til Beregning med Store Tall
I mange år har JavaScript-utviklere stått overfor en stille, men betydelig begrensning: språkets innebygde evne til å håndtere tall. Selv om det fungerer perfekt for hverdagslige beregninger, kom JavaScripts Number
-type til kort når den ble konfrontert med de virkelig massive heltallene som kreves innen felt som kryptografi, vitenskapelig databehandling og moderne datasystemer. Dette førte til en verden av omveier, tredjepartsbiblioteker og subtile, vanskelige å feilsøke presisjonsfeil.
Den æraen er over. Innføringen av BigInt som en innebygd primitiv type i JavaScript har revolusjonert hvordan vi jobber med store tall. Den gir en robust, ergonomisk og effektiv måte å utføre vilkårlig presisjons heltallsaritmetikk på, direkte i språket.
Denne omfattende guiden er for utviklere over hele verden. Vi vil dykke dypt inn i «hvorfor, hva og hvordan» for BigInt. Enten du bygger en finansiell applikasjon, samhandler med en blokkjede, eller bare prøver å forstå hvorfor din store, unike ID fra et API oppfører seg rart, vil denne artikkelen utstyre deg med kunnskapen for å mestre BigInt.
Problemet: Grensene for JavaScripts Number-type
Før vi kan sette pris på løsningen, må vi fullt ut forstå problemet. JavaScript har kun hatt én talltype gjennom det meste av sin historie: Number
-typen. Under panseret er den representert som et IEEE 754 dobbeltpresisjons 64-bits flyttall. Dette formatet er utmerket for å representere et bredt spekter av verdier, inkludert desimaler, men det har en kritisk begrensning når det gjelder heltall.
Møt MAX_SAFE_INTEGER
På grunn av sin flyttallsrepresentasjon, er det en grense for størrelsen på et heltall som kan representeres med perfekt presisjon. Denne grensen er eksponert via en konstant: Number.MAX_SAFE_INTEGER
.
Verdien er 253 - 1, som er 9,007,199,254,740,991. La oss for enkelhets skyld kalle det ni billiard.
Ethvert heltall innenfor området fra -Number.MAX_SAFE_INTEGER
til +Number.MAX_SAFE_INTEGER
regnes som et «trygt heltall». Dette betyr at det kan representeres nøyaktig og sammenlignes korrekt. Men hva skjer når vi går utenfor dette området?
La oss se det i praksis:
const maxSafe = Number.MAX_SAFE_INTEGER;
console.log(maxSafe); // 9007199254740991
// La oss legge til 1
console.log(maxSafe + 1); // 9007199254740992 - Dette ser riktig ut
// La oss legge til 1 til
console.log(maxSafe + 2); // 9007199254740992 - Uff da. Feil resultat.
// Det blir verre
console.log(maxSafe + 3); // 9007199254740994 - Vent, hva?
console.log(maxSafe + 4); // 9007199254740996 - Den hopper over tall!
// Likhetssjekk feiler også
console.log(maxSafe + 1 === maxSafe + 2); // true - Dette er matematisk feil!
Som du kan se, så snart vi overstiger Number.MAX_SAFE_INTEGER
, kan JavaScript ikke lenger garantere presisjonen i beregningene våre. Tallrepresentasjonen begynner å få hull, noe som fører til avrundingsfeil og ukorrekte resultater. Dette er et mareritt for applikasjoner som krever nøyaktighet med store heltall.
De Gamle Løsningene
I årevis stolte det globale utviklermiljøet på eksterne biblioteker for å løse dette problemet. Biblioteker som bignumber.js
, decimal.js
, og long.js
ble standardverktøy. De fungerte ved å representere store tall som strenger eller lister med siffer, og implementerte aritmetiske operasjoner i programvaren.
Selv om de var effektive, kom disse bibliotekene med kompromisser:
- Ytelseskostnad: Operasjoner var betydelig tregere enn innebygde tallberegninger.
- Pakkestørrelse: De økte størrelsen på applikasjonspakker, noe som er en bekymring for webytelse.
- Annen Syntaks: Utviklere måtte bruke objektmetoder (f.eks.
a.add(b)
) i stedet for standard aritmetiske operatorer (a + b
), noe som gjorde koden mindre intuitiv.
Introduksjon til BigInt: Den Innebygde Løsningen
BigInt ble introdusert i ES2020 for å løse dette problemet innebygd i språket. En BigInt
er en ny primitiv type i JavaScript som gir en måte å representere heltall større enn 253 - 1.
Hovedfunksjonen til BigInt er at størrelsen ikke er fast. Den kan representere vilkårlig store heltall, begrenset kun av tilgjengelig minne i vertssystemet. Dette eliminerer fullstendig presisjonsproblemene vi så med Number
-typen.
Hvordan Lage en BigInt
Det er to primære måter å lage en BigInt på:
- Legge til `n` etter et heltallsliteral: Dette er den enkleste og vanligste metoden.
- Bruke konstruktørfunksjonen `BigInt()`: Dette er nyttig når man konverterer en verdi fra en annen type, som en streng eller et tall.
Slik ser det ut i kode:
// 1. Bruke 'n'-suffikset
const myFirstBigInt = 900719925474099199n;
const anotherBigInt = 123456789012345678901234567890n;
// 2. Bruke BigInt()-konstruktøren
const fromString = BigInt("98765432109876543210");
const fromNumber = BigInt(100);
// Du kan sjekke typen
console.log(typeof myFirstBigInt); // "bigint"
console.log(typeof 100); // "number"
Med BigInt fungerer nå vår tidligere feilende beregning perfekt:
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"
// Likhet fungerer som forventet
console.log(maxSafePlusOne === maxSafePlusTwo); // false
Å Jobbe med BigInt: Syntaks og Operasjoner
BigInts oppfører seg mye som vanlige tall, men med noen avgjørende forskjeller som enhver utvikler må forstå for å unngå feil.
Aritmetiske Operasjoner
Alle standard aritmetiske operatorer fungerer med BigInts:
- Addisjon:
+
- Subtraksjon:
-
- Multiplikasjon:
*
- Eksponentiering:
**
- Modulus (Rest):
%
Den ene operatoren som oppfører seg annerledes er divisjon (`/`).
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
Forbeholdet med Divisjon
Siden BigInts bare kan representere hele tall, blir resultatet av en divisjon alltid avkortet (desimaldelen blir forkastet). Det rundes ikke av.
const a = 10n;
const b = 3n;
console.log(a / b); // 3n (ikke 3.333...n)
const c = 9n;
const d = 10n;
console.log(c / d); // 0n
Dette er en kritisk forskjell. Hvis du trenger å utføre beregninger med desimaler, er ikke BigInt det rette verktøyet. Du må da fortsette å bruke Number
eller et dedikert desimalbibliotek.
Sammenligning og Likhet
Sammenligningsoperatorer som >
, <
, >=
, og <=
fungerer sømløst mellom BigInts, og til og med mellom en BigInt og en Number.
console.log(10n > 5); // true
console.log(10n < 20); // true
console.log(10n > 20n); // false
Likhet er imidlertid mer nyansert og er en vanlig kilde til forvirring.
- Løs Likhet (`==`): Denne operatoren utfører typekonvertering. Den anser en BigInt og en Number med samme matematiske verdi for å være like.
- Streng Likhet (`===`): Denne operatoren utfører ikke typekonvertering. Siden BigInt og Number er forskjellige typer, vil den alltid returnere
false
når man sammenligner dem.
console.log(10n == 10); // true - Vær forsiktig med denne!
console.log(10n === 10); // false - Anbefales for klarhet.
console.log(0n == 0); // true
console.log(0n === 0); // false
Beste Praksis: For å unngå subtile feil, bruk alltid streng likhet (`===`) og vær eksplisitt om typene du sammenligner. Hvis du trenger å sammenligne en BigInt og en Number, er det ofte tydeligere å først konvertere den ene til den andre, men husk potensiell presisjonstap.
Typekonflikt: Et Strengt Skille
JavaScript håndhever en streng regel: du kan ikke blande BigInt- og Number-operander i de fleste aritmetiske operasjoner.
Forsøk på å gjøre dette vil resultere i en TypeError
. Dette er et bevisst designvalg for å forhindre at utviklere ved et uhell mister presisjon.
const myBigInt = 100n;
const myNumber = 50;
try {
const result = myBigInt + myNumber; // Dette vil kaste en feil
} catch (error) {
console.log(error); // TypeError: Cannot mix BigInt and other types, use explicit conversions
}
Den Riktige Tilnærmingen: Eksplisitt Konvertering
For å utføre en operasjon mellom en BigInt og en Number, må du eksplisitt konvertere en av dem.
const myBigInt = 100n;
const myNumber = 50;
// Konverter Number til BigInt (trygt)
const result1 = myBigInt + BigInt(myNumber);
console.log(result1); // 150n
// Konverter BigInt til Number (potensielt utrygt!)
const veryLargeBigInt = 900719925474099199n;
// Dette vil føre til presisjonstap!
const unsafeNumber = Number(veryLargeBigInt);
console.log(unsafeNumber); // 900719925474099200 - Verdien har blitt avrundet!
const safeResult = Number(100n) + myNumber;
console.log(safeResult); // 150
Kritisk Regel: Konverter kun en BigInt til en Number hvis du er helt sikker på at den passer innenfor det trygge heltallsområdet. Ellers, konverter alltid Number til BigInt for å opprettholde presisjon.
Praktiske Bruksområder for BigInt i en Global Kontekst
Behovet for BigInt er ikke et abstrakt akademisk problem. Det løser reelle utfordringer som utviklere står overfor i ulike internasjonale domener.
1. Høypresisjons Tidsstempler
JavaScript's `Date.now()` returnerer antall millisekunder siden Unix-epoken. Selv om dette er tilstrekkelig for mange webapplikasjoner, er det ikke granulært nok for høytytende systemer. Mange distribuerte systemer, databaser og loggrammeverk over hele verden bruker nanosekund-presisjons tidsstempler for å nøyaktig sortere hendelser. Disse tidsstemplene er ofte representert som 64-bits heltall, som er for store for Number
-typen.
// Et tidsstempel fra et høyoppløselig system (f.eks. i nanosekunder)
const nanoTimestampStr = "1670000000123456789";
// Bruk av Number resulterer i presisjonstap
const lostPrecision = Number(nanoTimestampStr);
console.log(lostPrecision); // 1670000000123456800 - Feil!
// Bruk av BigInt bevarer det perfekt
const correctTimestamp = BigInt(nanoTimestampStr);
console.log(correctTimestamp.toString()); // "1670000000123456789"
// Vi kan nå utføre nøyaktige beregninger
const oneSecondInNanos = 1_000_000_000n;
const nextSecond = correctTimestamp + oneSecondInNanos;
console.log(nextSecond.toString()); // "1670001000123456789"
2. Unike Identifikatorer (ID-er) fra API-er
Et veldig vanlig scenario er å samhandle med API-er som bruker 64-bits heltall for unike objekt-ID-er. Dette er et mønster som brukes av store globale plattformer som Twitter (Snowflake IDs) og mange databasesystemer (f.eks. BIGINT
-typen i SQL).
Når du henter data fra et slikt API, kan JSON-parseren i nettleseren eller Node.js-miljøet prøve å parse denne store ID-en som en Number
, noe som fører til datakorrupsjon før du i det hele tatt får sjansen til å jobbe med den.
// Et typisk JSON-svar fra et API
// Merk: ID-en er et stort tall, ikke en streng.
const jsonResponse = '{"id": 1367874743838343168, "text": "Hello, world!"}';
// Standard JSON.parse vil korrumpere ID-en
const parsedData = JSON.parse(jsonResponse);
console.log(parsedData.id); // 1367874743838343200 - Feil ID!
// Løsning: Sørg for at API-et sender store ID-er som strenger.
const safeJsonResponse = '{"id": "1367874743838343168", "text": "Hello, world!"}';
const safeParsedData = JSON.parse(safeJsonResponse);
const userId = BigInt(safeParsedData.id);
console.log(userId); // 1367874743838343168n - Riktig!
Dette er grunnen til at det er en allment akseptert beste praksis for API-er over hele verden å serialisere store heltalls-ID-er som strenger i JSON-nyttelaster for å sikre kompatibilitet med alle klienter.
3. Kryptografi
Moderne kryptografi er bygget på matematikk som involverer ekstremt store heltall. Algoritmer som RSA er avhengige av operasjoner med tall som er hundrevis eller til og med tusenvis av biter lange. BigInt gjør det mulig å utføre disse beregningene innebygd i JavaScript, noe som er essensielt for web-baserte kryptografiske applikasjoner, som de som bruker Web Crypto API eller implementerer protokoller i Node.js.
Selv om et fullstendig kryptografisk eksempel er komplekst, kan vi se en konseptuell demonstrasjon:
// To veldig store primtall (kun for demonstrasjonsformål)
const p = 1143400375533529n;
const q = 982451653n; // Et mindre ett for eksempelet
// I RSA multipliserer du dem for å få modulus
const n = p * q;
console.log(n.toString()); // "1123281328905333100311297"
// Denne beregningen ville vært umulig med Number-typen.
// BigInt håndterer det uten problemer.
4. Finansielle- og Blokkjedeprogrammer
Når man arbeider med finans, spesielt i konteksten av kryptovalutaer, er presisjon avgjørende. Mange kryptovalutaer, som Bitcoin, måler verdi i sin minste enhet (f.eks. satoshis). Den totale forsyningen av disse enhetene kan lett overstige Number.MAX_SAFE_INTEGER
. BigInt er det perfekte verktøyet for å håndtere disse store, presise mengdene uten å ty til flyttallsaritmetikk, som er utsatt for avrundingsfeil.
// 1 Bitcoin = 100 000 000 satoshis
const satoshisPerBTC = 100_000_000n;
// Total forsyning av Bitcoin er 21 millioner
const totalBTCSupply = 21_000_000n;
// Beregn totalt antall satoshis
const totalSatoshis = totalBTCSupply * satoshisPerBTC;
// 2,100,000,000,000,000 - Dette er 2,1 billiard
console.log(totalSatoshis.toString());
// Denne verdien er større enn Number.MAX_SAFE_INTEGER
console.log(totalSatoshis > BigInt(Number.MAX_SAFE_INTEGER)); // true
Avanserte Emner og Vanlige Fallgruver
Serialisering og JSON.stringify()
Et av de vanligste problemene utviklere støter på, er serialisering av objekter som inneholder BigInts. Som standard vet ikke JSON.stringify()
hvordan den skal håndtere bigint
-typen og vil kaste en 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
}
Løsning 1: Implementer en `toJSON`-metode
Du kan fortelle JSON.stringify
hvordan den skal håndtere BigInts ved å legge til en toJSON
-metode i BigInt.prototype
. Denne tilnærmingen endrer den globale prototypen, noe som kan være uønsket i noen delte miljøer, men den er veldig effektiv.
// En global endring. Bruk med omhu.
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"}'
Løsning 2: Bruk en replacer-funksjon
En tryggere, mer lokal tilnærming er å bruke replacer
-argumentet i JSON.stringify
. Denne funksjonen kalles for hvert nøkkel/verdi-par og lar deg transformere verdien før serialisering.
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"}'
Bitvise Operasjoner
BigInt støtter alle de bitvise operatorene du er kjent med fra Number
-typen: &
(AND), |
(OR), ^
(XOR), ~
(NOT), <<
(venstreskift), og >>
(tegn-propagerende høyreskift). Disse er spesielt nyttige når man jobber med lavnivå dataformater, tillatelser eller visse typer algoritmer.
const permissions = 5n; // 0101 i binær
const READ_PERMISSION = 4n; // 0100
const WRITE_PERMISSION = 2n; // 0010
// Sjekk om lesetillatelse er satt
console.log((permissions & READ_PERMISSION) > 0n); // true
// Sjekk om skrivetillatelse er satt
console.log((permissions & WRITE_PERMISSION) > 0n); // false
// Legg til skrivetillatelse
const newPermissions = permissions | WRITE_PERMISSION;
console.log(newPermissions); // 7n (som er 0111)
Ytelseshensyn
Selv om BigInt er utrolig kraftig, er det viktig å forstå ytelsesegenskapene:
- Number vs. BigInt: For heltall innenfor det trygge området, er standard
Number
-operasjoner betydelig raskere. Dette er fordi de ofte kan mappes direkte til maskinnivå-instruksjoner som behandles av datamaskinens CPU. BigInt-operasjoner, som er av vilkårlig størrelse, krever mer komplekse programvarebaserte algoritmer. - BigInt vs. Biblioteker: Innebygd
BigInt
er generelt mye raskere enn JavaScript-baserte biblioteker for store tall. Implementeringen er en del av JavaScript-motoren (som V8 eller SpiderMonkey) og er skrevet i et lavere nivå språk som C++, noe som gir den en betydelig ytelsesfordel.
Den Gylne Regel: Bruk Number
for alle numeriske beregninger med mindre du har en spesifikk grunn til å tro at verdiene kan overstige Number.MAX_SAFE_INTEGER
. Bruk BigInt
når du trenger dens kapabiliteter, ikke som en standard erstatning for alle tall.
Nettleser- og Miljøkompatibilitet
BigInt er en moderne JavaScript-funksjon, men støtten er nå utbredt over hele det globale økosystemet.
- Nettlesere: Støttet i alle store moderne nettlesere (Chrome 67+, Firefox 68+, Safari 14+, Edge 79+).
- Node.js: Støttet siden versjon 10.4.0.
For prosjekter som trenger å støtte veldig gamle miljøer, kan transpilerering med verktøy som Babel være et alternativ, men dette medfører en ytelsesstraff. Gitt den brede støtten i dag, kan de fleste nye prosjekter bruke BigInt innebygd uten bekymring.
Konklusjon og Beste Praksis
BigInt er et kraftig og essensielt tillegg til JavaScript-språket. Det gir en innebygd, effektiv og ergonomisk løsning på det langvarige problemet med aritmetikk for store heltall, og muliggjør en ny klasse applikasjoner bygget med JavaScript, fra kryptografi til høypresisjons databehandling.
For å bruke det effektivt og unngå vanlige fallgruver, ha disse beste praksisene i tankene:
- Bruk `n`-suffikset: Foretrekk
123n
-literalsyntaksen for å lage BigInts. Det er tydelig, konsist og unngår potensielt presisjonstap under opprettelsen. - Ikke Bland Typer: Husk at du ikke kan blande BigInt og Number i aritmetiske operasjoner. Vær eksplisitt med konverteringene dine:
BigInt()
ellerNumber()
. - Prioriter Presisjon: Når du konverterer mellom typer, foretrekk alltid å konvertere en
Number
til enBigInt
for å forhindre utilsiktet presisjonstap. - Bruk Streng Likhet: Bruk
===
i stedet for==
for sammenligninger for å unngå forvirrende oppførsel forårsaket av typekonvertering. - Håndter JSON-serialisering: Planlegg for serialisering av BigInts. Bruk en tilpasset
replacer
-funksjon iJSON.stringify
for en trygg, ikke-global løsning. - Velg Riktig Verktøy: Bruk
Number
for generell matematikk innenfor det trygge heltallsområdet for bedre ytelse. Bruk kunBigInt
når du virkelig trenger dens vilkårlige presisjonsegenskaper.
Ved å omfavne BigInt og forstå reglene, kan du skrive mer robuste, nøyaktige og kraftige JavaScript-applikasjoner som er i stand til å takle numeriske utfordringer i alle skalaer.