Įsisavinkite JavaScript BigInt tiksliam, didelio masto sveikųjų skaičių skaičiavimui. Išnagrinėkite sintaksę, panaudojimo atvejus kriptografijoje ir finansuose bei įveikite įprastas problemas, tokias kaip JSON serializavimas.
JavaScript BigInt: Išsamus didelių skaičių skaičiavimo vadovas
Daugelį metų JavaScript programuotojai susidūrė su tyliu, bet reikšmingu apribojimu: kalbos prigimtiniu gebėjimu dirbti su skaičiais. Nors puikiai tinka kasdieniams skaičiavimams, JavaScript Number
tipas suklupdavo susidūrus su tikrai didžiuliais sveikaisiais skaičiais, reikalingais tokiose srityse kaip kriptografija, moksliniai skaičiavimai ir šiuolaikinės duomenų sistemos. Tai vedė prie aplinkkelių, trečiųjų šalių bibliotekų ir subtilių, sunkiai derinamo tikslumo klaidų pasaulio.
Ta era baigėsi. BigInt įvedimas kaip naujo primityvaus JavaScript tipo sukėlė revoliuciją mūsų darbe su dideliais skaičiais. Jis suteikia tvirtą, ergonomišką ir efektyvų būdą atlikti savavališko tikslumo sveikųjų skaičių aritmetiką tiesiogiai pačioje kalboje.
Šis išsamus vadovas skirtas programuotojams visame pasaulyje. Gilinsimės į „kodėl, kas ir kaip“ apie BigInt. Nesvarbu, ar kuriate finansinę programą, sąveikaujate su blokų grandine, ar tiesiog bandote suprasti, kodėl jūsų didelis unikalus ID iš API elgiasi keistai, šis straipsnis suteiks jums žinių, reikalingų įvaldyti BigInt.
Problema: JavaScript „Number“ tipo ribos
Prieš įvertindami sprendimą, turime visiškai suprasti problemą. Didžiąją savo istorijos dalį JavaScript turėjo tik vieną skaičių tipą: Number
tipą. Po variklio dangčiu jis yra vaizduojamas kaip IEEE 754 dvigubo tikslumo 64 bitų slankiojo kablelio skaičius. Šis formatas puikiai tinka įvairioms vertėms, įskaitant dešimtaines, pavaizduoti, tačiau jis turi esminį apribojimą, kai kalbama apie sveikuosius skaičius.
Susipažinkite su MAX_SAFE_INTEGER
Dėl slankiojo kablelio vaizdavimo, egzistuoja riba, kokio dydžio sveikasis skaičius gali būti pavaizduotas su absoliučiu tikslumu. Ši riba yra prieinama per konstantą: Number.MAX_SAFE_INTEGER
.
Jos vertė yra 253 - 1, o tai yra 9,007,199,254,740,991. Trumpumo dėlei, vadinkime tai devyniais kvadrilijonais.
Bet koks sveikasis skaičius intervale nuo -Number.MAX_SAFE_INTEGER
iki +Number.MAX_SAFE_INTEGER
yra laikomas „saugiu sveikuoju skaičiumi“. Tai reiškia, kad jis gali būti tiksliai pavaizduotas ir teisingai palygintas. Bet kas nutinka, kai išeiname už šio intervalo ribų?
Pažiūrėkime, kaip tai veikia praktiškai:
const maxSafe = Number.MAX_SAFE_INTEGER;
console.log(maxSafe); // 9007199254740991
// Let's add 1 to it
console.log(maxSafe + 1); // 9007199254740992 - This looks correct
// Let's add another 1
console.log(maxSafe + 2); // 9007199254740992 - Uh oh. Incorrect result.
// It gets worse
console.log(maxSafe + 3); // 9007199254740994 - Wait, what?
console.log(maxSafe + 4); // 9007199254740996 - It's skipping numbers!
// Checking for equality also fails
console.log(maxSafe + 1 === maxSafe + 2); // true - This is mathematically wrong!
Kaip matote, kai viršijame Number.MAX_SAFE_INTEGER
, JavaScript nebegali garantuoti mūsų skaičiavimų tikslumo. Skaičių vaizdavime atsiranda spragų, dėl kurių atsiranda apvalinimo klaidų ir neteisingų rezultatų. Tai yra košmaras programoms, reikalaujančioms tikslumo dirbant su dideliais sveikaisiais skaičiais.
Seni sprendimo būdai
Daugelį metų pasaulinė programuotojų bendruomenė rėmėsi išorinėmis bibliotekomis, kad išspręstų šią problemą. Tokios bibliotekos kaip bignumber.js
, decimal.js
ir long.js
tapo standartiniais įrankiais. Jos veikė vaizduodamos didelius skaičius kaip eilutes ar skaitmenų masyvus ir įgyvendindamos aritmetines operacijas programinėje įrangoje.
Nors ir veiksmingos, šios bibliotekos turėjo savo kompromisų:
- Našumo praradimas: Operacijos buvo žymiai lėtesnės nei su prigimtiniais skaičiais.
- Paketo dydis: Jos pridėdavo svorio programų paketams, o tai yra svarbu interneto našumui.
- Skirtinga sintaksė: Programuotojai turėjo naudoti objektų metodus (pvz.,
a.add(b)
) vietoj standartinių aritmetinių operatorių (a + b
), todėl kodas tapdavo mažiau intuityvus.
Pristatome BigInt: prigimtinis sprendimas
BigInt buvo pristatytas ES2020, siekiant išspręsti šią problemą prigimtiniu būdu. BigInt
yra naujas primityvus tipas JavaScript kalboje, kuris suteikia būdą pavaizduoti sveikuosius skaičius, didesnius nei 253 - 1.
Pagrindinė BigInt savybė yra ta, kad jo dydis nėra fiksuotas. Jis gali pavaizduoti savavališkai didelius sveikuosius skaičius, ribojamus tik priimančios sistemos turima atmintimi. Tai visiškai pašalina tikslumo problemas, kurias matėme su Number
tipu.
Kaip sukurti BigInt
Yra du pagrindiniai būdai sukurti BigInt:
- Pridedant `n` prie skaičiaus literalo: Tai yra paprasčiausias ir labiausiai paplitęs metodas.
- Naudojant `BigInt()` konstruktoriaus funkciją: Tai naudinga konvertuojant vertę iš kito tipo, pavyzdžiui, eilutės ar skaičiaus.
Štai kaip tai atrodo kode:
// 1. Using the 'n' suffix
const myFirstBigInt = 900719925474099199n;
const anotherBigInt = 123456789012345678901234567890n;
// 2. Using the BigInt() constructor
const fromString = BigInt("98765432109876543210");
const fromNumber = BigInt(100);
// You can check the type
console.log(typeof myFirstBigInt); // "bigint"
console.log(typeof 100); // "number"
Su BigInt, mūsų anksčiau nepavykęs skaičiavimas dabar veikia puikiai:
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"
// Equality works as expected
console.log(maxSafePlusOne === maxSafePlusTwo); // false
Darbas su BigInt: sintaksė ir operacijos
BigInt elgiasi labai panašiai kaip įprasti skaičiai, tačiau su keliais esminiais skirtumais, kuriuos kiekvienas programuotojas turi suprasti, kad išvengtų klaidų.
Aritmetinės operacijos
Visi standartiniai aritmetiniai operatoriai veikia su BigInt:
- Sudėtis:
+
- Atimtis:
-
- Daugyba:
*
- Kėlimas laipsniu:
**
- Dalybos liekana (modulis):
%
Vienintelis operatorius, kuris elgiasi skirtingai, yra dalyba (`/`).
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
Dalybos ypatumas
Kadangi BigInt gali pavaizduoti tik sveikuosius skaičius, dalybos rezultatas visada yra nupjaunamas (trupmeninė dalis atmetama). Jis neapvalina.
const a = 10n;
const b = 3n;
console.log(a / b); // 3n (not 3.333...n)
const c = 9n;
const d = 10n;
console.log(c / d); // 0n
Tai yra esminis skirtumas. Jei jums reikia atlikti skaičiavimus su dešimtainėmis trupmenomis, BigInt nėra tinkamas įrankis. Jums reikėtų toliau naudoti Number
arba specializuotą dešimtainių skaičių biblioteką.
Palyginimas ir lygybė
Palyginimo operatoriai, tokie kaip >
, <
, >=
, ir <=
veikia sklandžiai tarp BigInt, ir netgi tarp BigInt ir Number.
console.log(10n > 5); // true
console.log(10n < 20); // true
console.log(10n > 20n); // false
Tačiau lygybė yra subtilesnė ir yra dažnas painiavos šaltinis.
- Laisva lygybė (`==`): Šis operatorius atlieka tipo konversiją. Jis laiko BigInt ir Number su ta pačia matematine verte lygiais.
- Griežta lygybė (`===`): Šis operatorius neatlieka tipo konversijos. Kadangi BigInt ir Number yra skirtingi tipai, jis visada grąžins
false
juos lyginant.
console.log(10n == 10); // true - Be careful with this!
console.log(10n === 10); // false - Recommended for clarity.
console.log(0n == 0); // true
console.log(0n === 0); // false
Geroji praktika: Siekdami išvengti subtilių klaidų, visada naudokite griežtą lygybę (`===`) ir būkite aiškūs dėl tipų, kuriuos lyginate. Jei reikia palyginti BigInt ir Number, dažnai aiškiau yra pirmiausia konvertuoti vieną į kitą, atsižvelgiant į galimą tikslumo praradimą.
Tipų neatitikimas: griežtas atskyrimas
JavaScript taiko griežtą taisyklę: negalima maišyti BigInt ir Number operandų daugumoje aritmetinių operacijų.
Bandymas tai padaryti sukels TypeError
. Tai yra sąmoningas dizaino sprendimas, siekiant apsaugoti programuotojus nuo netyčinio tikslumo praradimo.
const myBigInt = 100n;
const myNumber = 50;
try {
const result = myBigInt + myNumber; // This will throw an error
} catch (error) {
console.log(error); // TypeError: Cannot mix BigInt and other types, use explicit conversions
}
Teisingas požiūris: aiškus konvertavimas
Norėdami atlikti operaciją tarp BigInt ir Number, turite aiškiai konvertuoti vieną iš jų.
const myBigInt = 100n;
const myNumber = 50;
// Convert Number to BigInt (safe)
const result1 = myBigInt + BigInt(myNumber);
console.log(result1); // 150n
// Convert BigInt to Number (potentially unsafe!)
const veryLargeBigInt = 900719925474099199n;
// This will lose precision!
const unsafeNumber = Number(veryLargeBigInt);
console.log(unsafeNumber); // 900719925474099200 - The value has been rounded!
const safeResult = Number(100n) + myNumber;
console.log(safeResult); // 150
Svarbi taisyklė: Konvertuokite BigInt į Number tik tada, kai esate visiškai tikri, kad jis telpa į saugų sveikųjų skaičių diapazoną. Priešingu atveju, visada konvertuokite Number į BigInt, kad išlaikytumėte tikslumą.
Praktiniai BigInt panaudojimo atvejai pasauliniame kontekste
BigInt poreikis nėra abstrakti akademinė problema. Jis sprendžia realaus pasaulio iššūkius, su kuriais susiduria programuotojai įvairiose tarptautinėse srityse.
1. Didelio tikslumo laiko žymės
JavaScript `Date.now()` grąžina milisekundžių skaičių nuo Unix epochos pradžios. Nors to pakanka daugeliui interneto programų, tai nėra pakankamai tikslu didelio našumo sistemoms. Daugelis paskirstytų sistemų, duomenų bazių ir registravimo sistemų visame pasaulyje naudoja nanosekundžių tikslumo laiko žymes, kad tiksliai surikiuotų įvykius. Šios laiko žymės dažnai yra vaizduojamos kaip 64 bitų sveikieji skaičiai, kurie yra per dideli Number
tipui.
// A timestamp from a high-resolution system (e.g., in nanoseconds)
const nanoTimestampStr = "1670000000123456789";
// Using Number results in precision loss
const lostPrecision = Number(nanoTimestampStr);
console.log(lostPrecision); // 1670000000123456800 - Incorrect!
// Using BigInt preserves it perfectly
const correctTimestamp = BigInt(nanoTimestampStr);
console.log(correctTimestamp.toString()); // "1670000000123456789"
// We can now perform accurate calculations
const oneSecondInNanos = 1_000_000_000n;
const nextSecond = correctTimestamp + oneSecondInNanos;
console.log(nextSecond.toString()); // "1670001000123456789"
2. Unikalūs identifikatoriai (ID) iš API
Labai dažnas scenarijus yra sąveika su API, kurios naudoja 64 bitų sveikuosius skaičius unikaliems objektų ID. Tai yra modelis, naudojamas didžiosiose pasaulinėse platformose, tokiose kaip Twitter (Snowflake ID) ir daugelyje duomenų bazių sistemų (pvz., BIGINT
tipas SQL).
Kai gaunate duomenis iš tokios API, JSON analizatorius jūsų naršyklėje ar Node.js aplinkoje gali bandyti analizuoti šį didelį ID kaip Number
, o tai sukels duomenų sugadinimą dar prieš jums pradedant su jais dirbti.
// A typical JSON response from an API
// Note: The ID is a large number, not a string.
const jsonResponse = '{"id": 1367874743838343168, "text": "Hello, world!"}';
// Standard JSON.parse will corrupt the ID
const parsedData = JSON.parse(jsonResponse);
console.log(parsedData.id); // 1367874743838343200 - Wrong ID!
// Solution: Ensure the API sends large IDs as strings.
const safeJsonResponse = '{"id": "1367874743838343168", "text": "Hello, world!"}';
const safeParsedData = JSON.parse(safeJsonResponse);
const userId = BigInt(safeParsedData.id);
console.log(userId); // 1367874743838343168n - Correct!
Būtent todėl visame pasaulyje plačiai priimta gera praktika API yra serializuoti didelius sveikųjų skaičių ID kaip eilutes JSON duomenyse, siekiant užtikrinti suderinamumą su visais klientais.
3. Kriptografija
Šiuolaikinė kriptografija yra pagrįsta matematika, apimančia itin didelius sveikuosius skaičius. Algoritmai, tokie kaip RSA, remiasi operacijomis su skaičiais, kurie yra šimtų ar net tūkstančių bitų ilgio. BigInt leidžia atlikti šiuos skaičiavimus prigimtiniu būdu JavaScript, o tai yra būtina interneto pagrindu veikiančioms kriptografinėms programoms, pavyzdžiui, toms, kurios naudoja Web Crypto API arba įgyvendina protokolus Node.js.
Nors pilnas kriptografinis pavyzdys yra sudėtingas, galime pamatyti koncepcinę demonstraciją:
// Two very large prime numbers (for demonstration purposes only)
const p = 1143400375533529n;
const q = 982451653n; // A smaller one for the example
// In RSA, you multiply them to get the modulus
const n = p * q;
console.log(n.toString()); // "1123281328905333100311297"
// This calculation would be impossible with the Number type.
// BigInt handles it effortlessly.
4. Finansinės ir blokų grandinės programos
Dirbant su finansais, ypač kriptovaliutų kontekste, tikslumas yra svarbiausia. Daugelis kriptovaliutų, pavyzdžiui, Bitcoin, vertę matuoja mažiausiu savo vienetu (pvz., satoshiais). Bendra šių vienetų pasiūla gali lengvai viršyti Number.MAX_SAFE_INTEGER
. BigInt yra puikus įrankis dirbti su šiais dideliais, tiksliais kiekiais, nenaudojant slankiojo kablelio aritmetikos, kuri yra linkusi į apvalinimo klaidas.
// 1 Bitcoin = 100,000,000 satoshis
const satoshisPerBTC = 100_000_000n;
// Total supply of Bitcoin is 21 million
const totalBTCSupply = 21_000_000n;
// Calculate total satoshis
const totalSatoshis = totalBTCSupply * satoshisPerBTC;
// 2,100,000,000,000,000 - This is 2.1 quadrillion
console.log(totalSatoshis.toString());
// This value is larger than Number.MAX_SAFE_INTEGER
console.log(totalSatoshis > BigInt(Number.MAX_SAFE_INTEGER)); // true
Pažangesnės temos ir dažnos problemos
Serializavimas ir JSON.stringify()
Viena iš dažniausių problemų, su kuriomis susiduria programuotojai, yra objektų, kuriuose yra BigInt, serializavimas. Pagal nutylėjimą, JSON.stringify()
nežino, kaip apdoroti bigint
tipą, ir išmes 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
}
1 sprendimas: Įgyvendinkite `toJSON` metodą
Galite nurodyti JSON.stringify
, kaip elgtis su BigInt, pridėdami `toJSON` metodą prie BigInt.prototype
. Šis požiūris keičia globalų prototipą, kas gali būti nepageidautina kai kuriose bendrose aplinkose, tačiau tai yra labai efektyvu.
// A global patch. Use with consideration.
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"}'
2 sprendimas: Naudokite pakeitimo funkciją (replacer)
Saugesnis, labiau lokalizuotas požiūris yra naudoti `replacer` argumentą JSON.stringify
. Ši funkcija yra iškviečiama kiekvienai rakto/vertės porai ir leidžia transformuoti vertę prieš serializavimą.
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"}'
Operacijos su bitais
BigInt palaiko visus bitų operacijų operatorius, kuriuos esate įpratę matyti su Number
tipu: `&` (AND), `|` (OR), `^` (XOR), `~` (NOT), `<<` (postūmis į kairę) ir `>>` (ženklą išlaikantis postūmis į dešinę). Jie yra ypač naudingi dirbant su žemo lygio duomenų formatais, leidimais ar tam tikrų tipų algoritmais.
const permissions = 5n; // 0101 in binary
const READ_PERMISSION = 4n; // 0100
const WRITE_PERMISSION = 2n; // 0010
// Check if read permission is set
console.log((permissions & READ_PERMISSION) > 0n); // true
// Check if write permission is set
console.log((permissions & WRITE_PERMISSION) > 0n); // false
// Add write permission
const newPermissions = permissions | WRITE_PERMISSION;
console.log(newPermissions); // 7n (which is 0111)
Našumo aspektai
Nors BigInt yra neįtikėtinai galingas, svarbu suprasti jo našumo charakteristikas:
- Number vs. BigInt: Sveikiesiems skaičiams, telpantiems į saugų diapazoną, standartinės
Number
operacijos yra žymiai greitesnės. Taip yra todėl, kad jos dažnai gali būti tiesiogiai susietos su mašininio lygio instrukcijomis, kurias apdoroja kompiuterio procesorius. BigInt operacijos, būdamos savavališko dydžio, reikalauja sudėtingesnių programinių algoritmų. - BigInt vs. Bibliotekos: Prigimtinis
BigInt
paprastai yra daug greitesnis nei JavaScript pagrindu veikiančios didelių skaičių bibliotekos. Įgyvendinimas yra JavaScript variklio (pvz., V8 ar SpiderMonkey) dalis ir yra parašytas žemesnio lygio kalba, pvz., C++, kas suteikia jam didelį našumo pranašumą.
Auksinė taisyklė: Naudokite Number
visiems skaitiniams skaičiavimams, nebent turite konkrečios priežasties manyti, kad vertės gali viršyti Number.MAX_SAFE_INTEGER
. Naudokite BigInt
, kai jums reikia jo galimybių, o ne kaip numatytąjį pakaitalą visiems skaičiams.
Suderinamumas su naršyklėmis ir aplinkomis
BigInt yra moderni JavaScript funkcija, tačiau jos palaikymas dabar yra plačiai paplitęs visoje pasaulinėje ekosistemoje.
- Interneto naršyklės: Palaikoma visose pagrindinėse moderniose naršyklėse (Chrome 67+, Firefox 68+, Safari 14+, Edge 79+).
- Node.js: Palaikoma nuo 10.4.0 versijos.
Projektams, kuriems reikia palaikyti labai senas aplinkas, transpiliacija naudojant įrankius, tokius kaip Babel, gali būti pasirinkimas, tačiau tai kainuoja našumo praradimu. Atsižvelgiant į platų palaikymą šiandien, dauguma naujų projektų gali naudoti BigInt prigimtiniu būdu be rūpesčių.
Išvados ir gerosios praktikos
BigInt yra galingas ir esminis JavaScript kalbos papildymas. Jis suteikia prigimtinį, efektyvų ir ergonomišką sprendimą seniai egzistuojančiai didelių sveikųjų skaičių aritmetikos problemai, leidžiantį kurti naujos klasės programas su JavaScript, nuo kriptografijos iki didelio tikslumo duomenų apdorojimo.
Norėdami jį efektyviai naudoti ir išvengti dažnų problemų, laikykitės šių gerųjų praktikų:
- Naudokite `n` priesagą: Pirmenybę teikite
123n
literalo sintaksei kuriant BigInt. Ji yra aiški, glausta ir padeda išvengti galimo tikslumo praradimo kūrimo metu. - Nemaišykite tipų: Atminkite, kad negalima maišyti BigInt ir Number aritmetinėse operacijose. Būkite aiškūs su savo konversijomis:
BigInt()
arbaNumber()
. - Pirmenybę teikite tikslumui: Konvertuodami tarp tipų, visada teikite pirmenybę
Number
konvertavimui įBigInt
, kad išvengtumėte netyčinio tikslumo praradimo. - Naudokite griežtą lygybę: Naudokite
===
vietoj==
palyginimams, kad išvengtumėte painaus elgesio, kurį sukelia tipo konversija. - Tvarkykite JSON serializavimą: Planuokite BigInt serializavimą. Naudokite pasirinktinę
replacer
funkcijąJSON.stringify
saugiam, ne globaliam sprendimui. - Pasirinkite tinkamą įrankį: Naudokite
Number
bendros paskirties matematikai saugių sveikųjų skaičių diapazone, siekdami geresnio našumo. NaudokiteBigInt
tik tada, kai jums tikrai reikia jo savavališko tikslumo galimybių.
Priėmę BigInt ir suprasdami jo taisykles, galite rašyti tvirtesnes, tikslesnes ir galingesnes JavaScript programas, galinčias įveikti bet kokio masto skaitinius iššūkius.