Nedávno vyšla na Rootu zprávička, že v beta verzi Firefoxu je již integrována podpora nového formátu čísel BigInt. V prohlížečích na bázi Chrome (projekt Chromium) již tato podpora nějakou dobu je. Ale co vlastně je BigInt a k čemu můžeme potřebovat další celočíselný typ?
Numerické datové typy v JavaScriptu
JavaScript nabízí jen velmi málo způsobů, jak uchovávat číselné hodnoty. Když pomineme objekty typu Int8Array a podobné, měli jsme donedávna k dispozici jen jeden datový typ určený pro uložení číselné hodnoty, a to Number. Ovšem tento datový typ má vážná omezení.
Datový typ Number je vnitřně reprezentován ve formátu binary64 podle normy IEEE 754. Je to klasický double používaný například v C. Na mantisu je rezervováno 52 bitů, exponent má 11 bitů, poslední bit zůstává na znaménko. Nejvyšší číslo, které můžeme do proměnné tohoto typu uložit, je přibližně 1.8×10308.
Ovšem pokud proměnnou typu Number chceme používat na přesné počítání s celými čísly, maximální hodnota, se kterou můžeme počítat, je to 253 = 9_007_199_254_740_992, což je přibližně 9 biliard. K této hodnotě se už nedá přičíst jednička. Nejmenší rozdíl mezi dvěma čísly je 2. Od 18 biliard výše už to bude 4 a tak dále. Pokud přičteme jedničku, hodnota proměnné zůstane stejná, protože číslo o jedničku vyšší nelze zakódovat, výsledek se tedy zaokrouhlí dolů.
Na co vlastně potřebujeme celá čísla?
Na první pohled by se mohlo zdát, že na počítání s penězi bude datový typ Number úplně ideální. Můžeme počítat na dvě desetinná místa – koruny a haléře, nebo euro a centy. Ale pozor. Zkuste si spočítat, kolik je 0,10+0,20. Je to 0,30? Není! Hodnotu 0,10 totiž nelze do datového typu Number zakódovat přesně. Bude to přibližně 0,099999999999999991673327315. Ani čísla 0,20 a 0,30 nebudou zakódována přesně. Chyby v zaokrouhlování způsobí, že 0,10+0,20–0,30=5,551115123125783e-17.
Jak ovšem vyřešit tuto nepříjemnou situaci? Jednoduše budeme počítat v haléřích. Stačí, abychom zobrazovací a čtecí rutiny upravili tak, aby tiskly a akceptovaly desetinnou tečku nebo čárku před druhé místo zprava a je vystaráno. Ale je tady problém i na druhé straně. Problém s velkými čísly.
Kdybychom používali tento datový typ pro počítání s penězi, mohlo by docházet k nepříjemným chybám. Například bychom mohli z konta, na kterém je 10 biliard haléřů, tj. 100 biliónů korun, vybírat každou nanosekundu 1 haléř a stav konta by byl stále 100 biliónů i přesto, že vybíráme 10 miliónů korun každou sekundu.
Stejně tak jakékoliv indexy, čítače nebo číselné kódy jsou omezeny na hodnoty menší než 9 biliard.
Celá čísla – z čeho (ne)můžeme vybírat
V nejrůznějších programovacích jazycích existuje několik typů celých čísel. Liší se v počtu bytů, ve kterých jsou uložena. Typicky jsou čísla uložena v 1, 2, 4 nebo 8 bytech a na základě velikosti mohou mít různě velké rozsahy hodnot. Od přibližně +/-127 v jednom byte přes +/-32767 a +/-2 miliardy až po přibližně +/-9 triliónů v 8 bytech (v záporné větvi je rozsah o jedničku větší, takže –128 až +127 a tak dále). Můžeme též vybrat typ bez znaménka a posunout rozsah do kladné poloviny, takže od 0–255 po 0–18 triliónů.
Společná vlastnost všech těchto formátů je ta, že vždy musíme dopředu rozhodnout, jaký rozsah budeme potřebovat. Někdy je to jednoduché. Například na zakódování jednoho znaku stačí 1 byte. Písmen je přece pouze 26. A nebo ne? S problémem kódování znaků se potýkáme dodnes.
Nebo na počítání zobrazení videa na YouTube přece musí stačit 4 byty (se znaménkem). Žádné video přece nebude mít více než dvě miliardy shlédnutí. Před pár lety klip Gangnam Style překročil tuto hranici a youtube začal zobrazovat nesmyslné záporné číslo. Samozřejmě to rychle opravili a dnes má toto video přibližně 3,3 miliardy zobrazení. A to už vůbec nebudeme mluvit o tom, že čtyři miliardy IP adres přece musí stačit napořád.
Takže i klasická celá čísla mají své problémy, ale to nás v JavaScriptu nemusí příliš trápit, tyhle typy tady nejsou. Najdeme je jako součást polí Int8Array a podobných, ale přímo použitelný typ integer tady není. Ovšem teď máme něco lepšího.
Představujeme BigInt
Prohlížeče založené na jádře Chrome od verze 67 z loňského roku mají nový datový typ BigInt. Do Firefoxu se tato novinka dostane ve verzi 68. Aktuálně je v beta verzi.
Do proměnné typu BigInt můžeme vložit libovolně velké celé číslo. Toto číslo bude uloženo beze ztráty přesnosti. Jediné omezení, které máme, je představováno velikostí virtuální paměti a velikostí swapového oddílu. Jak je toto číslo s arbitrární přesností uloženo? V Chrome je to poměrně jednoduchá struktura, ve Firefoxu to bude pravděpodobně implementováno podobně:
{ type: 'BigInt', sign: 0, num_digits: 3, digits: [0x12345678, 0xabcdef12, 0xdeadbeef], }
Podle velikosti čísla, které potřebujeme uložit, se alokuje příslušný počet „číslic“. Číslice v tomto případě mají 232 hodnot, program s nimi počítá stejně, jako jsme my zvyklí počítat s číslicemi s deseti hodnotami 0–9. Typ BigInt je vždy signed, t.j. se znaménkem.
Použití BigInt v programu
Čísla typu BigInt se v JavaScriptu označují příponou „n“. Například 123n. Nebo 123456789123456789123456789n. Existují pro ně stejné operace, jako pro čísla typu Number, takže je můžeme sčítat, odčítat, násobit, dělit, umocňovat a podobně. Dokonce můžeme provádět i bitový součet a součin (& | ^) a bitové posuvy s výjimkou unsigned posuvu >>>, protože BigInt je vždy signed. Jediný operátor, který neexistuje pro BigInt, je unární plus. Unární mínus samozřejmě funguje.
Logické operátory se chovají, jako by bylo číslo uloženo jako dvojkový doplněk i když je v praxi uloženo jinak. Takže například –2n ^ 2n == –4n. V případě dělení se výsledek zaokrouhluje na nejbližší celé číslo směrem k nule, stejně jako jsme zvyklí z jiných programovacích jazyků.
Pozor, nemůžeme kombinovat typ BigInt a Number v jednom, výrazu. Všechna čísla ve výrazu musí být stejného typu. Buď všechna Number nebo všechna BigInt. V opačném případě program skončí s chybou. Není tady žádná automatická konverze.
Pro konverzi stringu nebo čísla typu Number na číslo typu BigInt se používá BigInt()
. Pro opačný převod použijeme logicky Number()
. BigInt, stejně jako Number se automaticky konvertuje na string v případě, že ho použijeme jako string, například při sčítání se stringem. Poslední funkce, o které bych se chtěl zmínit, je „ořezání“ BigInt na určitý počet bitů. Je to BigInt.asIntN(šířka, hodnota)
a BigInt.asUintN(šířka,
hodnota)
.
Příklady:
9007199254740992+1 => 9007199254740992 (chybný výsledek) 9007199254740992n+1n => 9007199254740993n 123n+1 => Error, kombinujeme BigInt a Number 101n/2n => 50n (zaokrouhleno směrem k nule) BigInt(123) => 123n Number(123n) => 123 Number(9007199254740993n) => 9007199254740992 (ztráta přesnosti) 123n+"str" => "123str"
Pojďme si hrát
Jak si nejjednodušeji tyto nové informace ověřit, vyzkoušet a pohrát si s tímto novým datovým typem? Nejjednodušší bude najít online službu, kde si můžeme napsat kousek javascriptového kódu, spustit si ho a podívat se na výsledek. Podobných služeb existuje celá řada, ovšem některé z nich, jako například Online JavaScript Editor provádí kód na serveru a ten není kompatibilní s BigInt.
Ale například js.do dělá přesně to, co potřebujeme. Do levé poloviny stránky píšeme kód, v pravé polovině vidíme výsledek kódu provedeného v našem lokálním prohlížeči. Můžeme si takto ověřit i to, že v aktuální stabilní verzi Firefox V67.0.1 BigInt ještě nefunguje, zatímco v Chromiu funguje přesně podle dokumentace.
Využití BigInt v praxi
Zanedlouho vyjde Firefox s podporou BigInt a Chrome jej už přibližně rok podporuje. Mohlo by se tedy zdát, že v cestě použití BigInt v reálném životě už nic nestojí. Ovšem opak je pravdou. Mnoho uživatelů z nejrůznějších důvodů ještě dlouho nebude mít poslední verzi prohlížeče nebo používá operační systém, který už není podporovaný. Takže ještě dlouho nebude možné BigInt použít v reálném nasazení, kde budeme potřebovat, aby fungoval široké veřejnosti. Samozřejmě pokud se bude jednat o lokální nasazení v intranetu, kde potřebujeme, aby to fungovalo úzkému kruhu uživatelů, můžeme pomalu začít BigInt nasazovat.