Čísla

V moderním JavaScriptu existují dva typy čísel:

  1. Běžná čísla v JavaScriptu jsou uložena v 64bitovém formátu IEEE-754, známém také jako „čísla s plovoucí desetinnou čárkou s dvojitou přesností“. Toto jsou čísla, která používáme většinu času, a budeme o nich mluvit v této kapitole.

  2. BigInt čísla představují celá čísla libovolné délky. Někdy jsou potřeba, protože běžné celé číslo nemůže bezpečně překročit (253-1) nebo být menší než -(253-1) , jak jsme zmínili dříve v kapitole Datové typy. Protože biginty se používají v několika speciálních oblastech, věnujeme jim zvláštní kapitolu BigInt.

Takže zde budeme mluvit o běžných číslech. Rozšiřme své znalosti o nich.

Více způsobů, jak napsat číslo

Představte si, že potřebujeme napsat 1 miliardu. Zřejmý způsob je:

let billion = 1000000000;

Můžeme také použít podtržítko _ jako oddělovač:

let billion = 1_000_000_000;

Zde podtržítko _ hraje roli „syntaktického cukru“, činí číslo čitelnějším. JavaScript engine jednoduše ignoruje _ mezi číslicemi, takže je to přesně stejná miliarda jako výše.

Ve skutečném životě se však snažíme vyhnout psaní dlouhých sekvencí nul. Na to jsme příliš líní. Pokusíme se napsat něco jako "1bn" za miliardu nebo "7.3bn" za 7 miliard 300 milionů. Totéž platí pro většinu velkých čísel.

V JavaScriptu můžeme číslo zkrátit přidáním písmene "e" k němu a zadáním počtu nul:

let billion = 1e9; // 1 billion, literally: 1 and 9 zeroes

alert( 7.3e9 ); // 7.3 billions (same as 7300000000 or 7_300_000_000)

Jinými slovy e vynásobí číslo 1 s daným počtem nul.

1e3 === 1 * 1000; // e3 means *1000
1.23e6 === 1.23 * 1000000; // e6 means *1000000

Nyní napíšeme něco velmi malého. Řekněme 1 mikrosekundu (jedna miliontina sekundy):

let mсs = 0.000001;

Stejně jako předtím, pomocí "e" může pomoct. Pokud bychom se chtěli vyhnout psaní nul explicitně, mohli bychom napsat totéž jako:

let mcs = 1e-6; // five zeroes to the left from 1

Pokud počítáme nuly v 0.000001 , je jich 6. Takže přirozeně je to 1e-6 .

Jinými slovy, záporné číslo za "e" znamená dělení 1 s daným počtem nul:

// -3 divides by 1 with 3 zeroes
1e-3 === 1 / 1000; // 0.001

// -6 divides by 1 with 6 zeroes
1.23e-6 === 1.23 / 1000000; // 0.00000123

// an example with a bigger number
1234e-2 === 1234 / 100; // 12.34, decimal point moves 2 times

Hexadecimální, binární a osmičková čísla

Hexadecimální čísla se v JavaScriptu široce používají k reprezentaci barev, kódování znaků a pro mnoho dalších věcí. Přirozeně tedy existuje kratší způsob, jak je zapsat:0x a potom číslo.

Například:

alert( 0xff ); // 255
alert( 0xFF ); // 255 (the same, case doesn't matter)

Binární a osmičkové číselné soustavy se používají zřídka, ale jsou také podporovány pomocí 0b a 0o předpony:

let a = 0b11111111; // binary form of 255
let b = 0o377; // octal form of 255

alert( a == b ); // true, the same number 255 at both sides

Existují pouze 3 číselné soustavy s takovou podporou. Pro ostatní číselné soustavy bychom měli použít funkci parseInt (což uvidíme později v této kapitole).

toString(základ)

Metoda num.toString(base) vrátí řetězcovou reprezentaci num v číselné soustavě s daným base .

Například:

let num = 255;

alert( num.toString(16) ); // ff
alert( num.toString(2) ); // 11111111

base se může lišit od 2 na 36 . Ve výchozím nastavení je to 10 .

Běžné případy použití jsou:

  • základ=16 používá se pro hexadecimální barvy, kódování znaků atd., číslice mohou být 0..9 nebo A..F .

  • základ=2 je většinou pro ladění bitových operací, číslice mohou být 0 nebo 1 .

  • základ=36 je maximum, číslic může být 0..9 nebo A..Z . K vyjádření čísla se používá celá latinská abeceda. Vtipný, ale užitečný případ pro 36 je, když potřebujeme změnit dlouhý číselný identifikátor na něco kratšího, například vytvořit krátkou adresu URL. Lze jej jednoduše vyjádřit v číselné soustavě se základem 36 :

    alert( 123456..toString(36) ); // 2n9c
Dvě tečky pro volání metody

Vezměte prosím na vědomí, že dvě tečky v 123456..toString(36) není překlep. Pokud chceme volat metodu přímo na číslo, například toString ve výše uvedeném příkladu pak musíme umístit dvě tečky .. po něm.

Pokud bychom umístili jednu tečku:123456.toString(36) , pak by došlo k chybě, protože syntaxe JavaScriptu implikuje desetinnou část za první tečkou. A pokud umístíme ještě jednu tečku, pak JavaScript ví, že desetinná část je prázdná a nyní jde o metodu.

Také mohl psát (123456).toString(36) .

Zaokrouhlení

Jednou z nejpoužívanějších operací při práci s čísly je zaokrouhlování.

Existuje několik vestavěných funkcí pro zaokrouhlování:

Math.floor
Zaokrouhluje dolů:3.1 se změní na 3 a -1.1 se změní na -2 .
Math.ceil
Zaokrouhluje nahoru:3.1 se změní na 4 a -1.1 se změní na -1 .
Math.round
Zaokrouhlí na nejbližší celé číslo:3.1 se změní na 3 , 3.6 se změní na 4 , střední velikost písmen:3.5 zaokrouhluje nahoru na 4 taky.
Math.trunc (nepodporuje Internet Explorer)
Odebere cokoli za desetinnou čárkou bez zaokrouhlení:3.1 se změní na 3 , -1.1 se změní na -1 .

Zde je tabulka shrnující rozdíly mezi nimi:

Math.floor Math.ceil Math.round Math.trunc
3.1 3 4 3 3
3.6 3 4 4 3
-1.1 -2 -1 -1 -1
-1.6 -2 -1 -2 -1

Tyto funkce pokrývají všechny možné způsoby zacházení s desetinnou částí čísla. Ale co když bychom chtěli číslo zaokrouhlit na n-th číslice za desetinnou čárkou?

Například máme 1.2345 a chcete jej zaokrouhlit na 2 číslice, dostanete pouze 1.23 .

Existují dva způsoby, jak to udělat:

  1. Vynásobte a rozdělte.

    Chcete-li například zaokrouhlit číslo na 2. číslici za desetinnou čárkou, můžeme číslo vynásobit 100 , zavolejte funkci zaokrouhlení a poté ji vydělte.

    let num = 1.23456;
    
    alert( Math.round(num * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
  2. Metoda toFixed(n) zaokrouhlí číslo na n číslic za tečkou a vrátí řetězcovou reprezentaci výsledku.

    let num = 12.34;
    alert( num.toFixed(1) ); // "12.3"

    Tím se zaokrouhlí nahoru nebo dolů na nejbližší hodnotu, podobně jako Math.round :

    let num = 12.36;
    alert( num.toFixed(1) ); // "12.4"

    Upozorňujeme, že výsledek toFixed je řetězec. Pokud je desetinná část kratší, než je požadováno, jsou na konec připojeny nuly:

    let num = 12.34;
    alert( num.toFixed(5) ); // "12.34000", added zeroes to make exactly 5 digits

    Můžeme jej převést na číslo pomocí unárního plus nebo Number() zavolejte, např. napište +num.toFixed(5) .

Nepřesné výpočty

Interně je číslo reprezentováno v 64bitovém formátu IEEE-754, takže je k uložení čísla přesně 64 bitů:52 z nich se používá k uložení číslic, 11 z nich k uložení pozice desetinné čárky a 1 bit je pro znamení.

Pokud je číslo opravdu velké, může přetéct 64bitové úložiště a stát se speciální číselnou hodnotou Infinity :

alert( 1e500 ); // Infinity

Co může být trochu méně zřejmé, ale stává se to poměrně často, je ztráta přesnosti.

Zvažte tento (falešný!) test rovnosti:

alert( 0.1 + 0.2 == 0.3 ); // false

To je pravda, pokud zkontrolujeme, zda je součet 0.1 a 0.2 je 0.3 , dostaneme false .

Zvláštní! Co to tedy je, když ne 0.3 ?

alert( 0.1 + 0.2 ); // 0.30000000000000004

Au! Představte si, že vytváříte e-shop a návštěvník zadá $0.10 a $0.20 zboží do jejich košíku. Celková objednávka bude $0.30000000000000004 . To by každého překvapilo.

Ale proč se to děje?

Číslo je uloženo v paměti ve své binární podobě, posloupnosti bitů – jedniček a nul. Ale zlomky jako 0.1 , 0.2 které v desítkové soustavě vypadají jednoduše, jsou ve skutečnosti nekonečné zlomky ve své binární podobě.

Co je 0.1 ? Je to jedna dělená deseti 1/10 , jedna desetina. V desítkové číselné soustavě jsou taková čísla snadno reprezentovatelná. Porovnejte to s jednou třetinou:1/3 . Stává se z něj nekonečný zlomek 0.33333(3) .

Takže dělení mocninami 10 zaručeně funguje dobře v desítkové soustavě, ale dělení 3 není. Ze stejného důvodu je v binární číselné soustavě dělení mocninou 2 zaručeně funguje, ale 1/10 se stává nekonečným binárním zlomkem.

Neexistuje žádný způsob, jak uložit přesně 0,1 nebo přesně 0,2 pomocí dvojkové soustavy, stejně jako neexistuje způsob, jak uložit jednu třetinu jako desetinný zlomek.

Číselný formát IEEE-754 to řeší zaokrouhlením na nejbližší možné číslo. Tato pravidla zaokrouhlování nám normálně neumožňují vidět „malou ztrátu přesnosti“, ale existuje.

Můžeme to vidět v akci:

alert( 0.1.toFixed(20) ); // 0.10000000000000000555

A když sečteme dvě čísla, jejich „ztráty přesnosti“ se sečtou.

Proto 0.1 + 0.2 není přesně 0.3 .

Nejen JavaScript

Stejný problém existuje v mnoha jiných programovacích jazycích.

PHP, Java, C, Perl, Ruby dávají přesně stejný výsledek, protože jsou založeny na stejném číselném formátu.

Můžeme problém obejít? Jistě, nejspolehlivější metodou je zaokrouhlit výsledek pomocí metody toFixed(n):

let sum = 0.1 + 0.2;
alert( sum.toFixed(2) ); // "0.30"

Vezměte prosím na vědomí, že toFixed vždy vrátí řetězec. Zajišťuje, že má za desetinnou čárkou 2 číslice. To je vlastně výhodné, pokud máme e-shop a potřebujeme zobrazit $0.30 . V ostatních případech můžeme použít jednočlenné plus k vynucení čísla:

let sum = 0.1 + 0.2;
alert( +sum.toFixed(2) ); // 0.3

Můžeme také dočasně vynásobit čísla 100 (nebo větším číslem), abychom je proměnili na celá čísla, spočítali a pak vydělili zpět. Když pak počítáme s celými čísly, chyba se poněkud zmenšuje, ale stále ji dostáváme na dělení:

alert( (0.1 * 10 + 0.2 * 10) / 10 ); // 0.3
alert( (0.28 * 100 + 0.14 * 100) / 100); // 0.4200000000000001

Přístup násobení/rozdělení tedy snižuje chybu, ale neodstraňuje ji úplně.

Někdy jsme se mohli pokusit zlomkům vůbec vyhnout. Jako když jednáme s obchodem, pak můžeme uložit ceny v centech místo v dolarech. Co když ale uplatníme slevu 30 %? V praxi je úplné vyloučení frakcí jen zřídka možné. Jednoduše je zakulatíte, abyste v případě potřeby odřízli „ocásky“.

Vtipná věc

Zkuste spustit toto:

// Hello! I'm a self-increasing number!
alert( 9999999999999999 ); // shows 10000000000000000

Toto trpí stejným problémem:ztrátou přesnosti. Číslo má 64 bitů, 52 z nich lze použít k uložení číslic, ale to nestačí. Takže nejméně významné číslice zmizí.

JavaScript v takových událostech nespouští chybu. Snaží se, aby se číslo vešlo do požadovaného formátu, ale tento formát bohužel není dostatečně velký.

Dvě nuly

Dalším vtipným důsledkem vnitřní reprezentace čísel je existence dvou nul:0 a -0 .

Je to proto, že znaménko je reprezentováno jedním bitem, takže jej lze nebo nelze nastavit pro jakékoli číslo včetně nuly.

Ve většině případů je rozdíl nepostřehnutelný, protože operátory jsou vhodné k tomu, aby s nimi zacházely stejně.

Testy:isFinite a isNaN

Pamatujete si tyto dvě speciální číselné hodnoty?

  • Infinity (a -Infinity ) je speciální číselná hodnota, která je větší (menší) než cokoliv jiného.
  • NaN představuje chybu.

Patří do typu number , ale nejsou to „normální“ čísla, takže pro jejich kontrolu existují speciální funkce:

  • isNaN(value) převede svůj argument na číslo a poté jej otestuje na NaN :

    alert( isNaN(NaN) ); // true
    alert( isNaN("str") ); // true

    Ale potřebujeme tuto funkci? Nemůžeme prostě použít srovnání === NaN ? Bohužel ne. Hodnota NaN je unikátní v tom, že se nerovná ničemu, včetně sebe sama:

    alert( NaN === NaN ); // false
  • isFinite(value) převede svůj argument na číslo a vrátí true pokud je to běžné číslo, nikoli NaN/Infinity/-Infinity :

    alert( isFinite("15") ); // true
    alert( isFinite("str") ); // false, because a special value: NaN
    alert( isFinite(Infinity) ); // false, because a special value: Infinity

Někdy isFinite se používá k ověření, zda je hodnota řetězce běžné číslo:

let num = +prompt("Enter a number", '');

// will be true unless you enter Infinity, -Infinity or not a number
alert( isFinite(num) );

Vezměte prosím na vědomí, že prázdný řetězec nebo řetězec obsahující pouze mezery je považován za 0 ve všech numerických funkcích včetně isFinite .

Number.isNaN a Number.isFinite

Metody Number.isNaN a Number.isFinite jsou „přísnější“ verze isNaN a isFinite funkcí. Nepřevádějí automaticky svůj argument na číslo, ale zkontrolují, zda patří do number zadejte místo toho.

  • Number.isNaN(value) vrátí true pokud argument patří do number zadejte a je to NaN . V každém jiném případě vrátí false .

    alert( Number.isNaN(NaN) ); // true
    alert( Number.isNaN("str" / 2) ); // true
    
    // Note the difference:
    alert( Number.isNaN("str") ); // false, because "str" belongs to the string type, not the number type
    alert( isNaN("str") ); // true, because isNaN converts string "str" into a number and gets NaN as a result of this conversion
  • Number.isFinite(value) vrátí true pokud argument patří do number typ a není to NaN/Infinity/-Infinity . V každém jiném případě vrátí false .

    alert( Number.isFinite(123) ); // true
    alert( Number.isFinite(Infinity) ); //false
    alert( Number.isFinite(2 / 0) ); // false
    
    // Note the difference:
    alert( Number.isFinite("123") ); // false, because "123" belongs to the string type, not the number type
    alert( isFinite("123") ); // true, because isFinite converts string "123" into a number 123

Svým způsobem Number.isNaN a Number.isFinite jsou jednodušší a přímočařejší než isNaN a isFinite funkcí. V praxi však isNaN a isFinite se většinou používají, protože jsou kratší na psaní.

Porovnání s Object.is

Existuje speciální vestavěná metoda Object.is který porovnává hodnoty jako === , ale je spolehlivější pro dva okrajové případy:

  1. Funguje s NaN :Object.is(NaN, NaN) === true , to je dobrá věc.
  2. Hodnoty 0 a -0 jsou různé:Object.is(0, -0) === false , technicky je to pravda, protože interně má číslo znaménkový bit, který se může lišit, i když všechny ostatní bity jsou nuly.

Ve všech ostatních případech Object.is(a, b) je stejný jako a === b .

Zmiňujeme Object.is zde, protože se často používá ve specifikaci JavaScriptu. Když interní algoritmus potřebuje porovnat dvě hodnoty, aby byly přesně stejné, použije Object.is (interně nazývané SameValue).

parseInt a parseFloat

Číselný převod pomocí plus + nebo Number() je přísná. Pokud hodnota není přesně číslo, selže:

alert( +"100px" ); // NaN

Jedinou výjimkou jsou mezery na začátku nebo na konci řetězce, protože jsou ignorovány.

Ale v reálném životě máme často hodnoty v jednotkách, jako je "100px" nebo "12pt" v CSS. V mnoha zemích také následuje symbol měny za částkou, takže máme "19€" a chtěli byste z toho extrahovat číselnou hodnotu.

To je to, co parseInt a parseFloat jsou pro.

„Přečtou“ číslo z řetězce, dokud to neumějí. V případě chyby je vráceno shromážděné číslo. Funkce parseInt vrátí celé číslo, zatímco parseFloat vrátí číslo s plovoucí desetinnou čárkou:

alert( parseInt('100px') ); // 100
alert( parseFloat('12.5em') ); // 12.5

alert( parseInt('12.3') ); // 12, only the integer part is returned
alert( parseFloat('12.3.4') ); // 12.3, the second point stops the reading

Existují situace, kdy parseInt/parseFloat vrátí NaN . Stává se to, když nelze přečíst žádné číslice:

alert( parseInt('a123') ); // NaN, the first symbol stops the process
Druhý argument z parseInt(str, radix)

parseInt() funkce má volitelný druhý parametr. Specifikuje základ číselné soustavy, tedy parseInt může také analyzovat řetězce hexadecimálních čísel, binárních čísel a tak dále:

alert( parseInt('0xff', 16) ); // 255
alert( parseInt('ff', 16) ); // 255, without 0x also works

alert( parseInt('2n9c', 36) ); // 123456

Další matematické funkce

JavaScript má vestavěný objekt Math, který obsahuje malou knihovnu matematických funkcí a konstant.

Několik příkladů:

Math.random()

Vrátí náhodné číslo od 0 do 1 (kromě 1).

alert( Math.random() ); // 0.1234567894322
alert( Math.random() ); // 0.5435252343232
alert( Math.random() ); // ... (any random numbers)
Math.max(a, b, c...) a Math.min(a, b, c...)

Vrátí největší a nejmenší z libovolného počtu argumentů.

alert( Math.max(3, 5, -10, 0, 1) ); // 5
alert( Math.min(1, 2) ); // 1
Math.pow(n, power)

Vrátí n zvýšen na daný výkon.

alert( Math.pow(2, 10) ); // 2 in power 10 = 1024

V Math je více funkcí a konstant objekt, včetně trigonometrie, kterou najdete v dokumentaci k objektu Math.

Shrnutí

Chcete-li psát čísla s mnoha nulami:

  • Připojit "e" s nulami počítejte k číslu. Jako:123e6 je stejný jako 123 se 6 nulami 123000000 .
  • Záporné číslo za "e" způsobí, že se číslo vydělí 1 danými nulami. Např. 123e-6 znamená 0.000123 (123 miliontiny).

Pro různé číselné soustavy:

  • Umí psát čísla přímo v hexadecimálním formátu (0x ), osmičková (0o ) a binární (0b ) systémy.
  • parseInt(str, base) analyzuje řetězec str na celé číslo v číselné soustavě s daným base , 2 ≤ base ≤ 36 .
  • num.toString(base) převede číslo na řetězec v číselné soustavě s daným base .

Pro běžné testy čísel:

  • isNaN(value) převede svůj argument na číslo a poté jej otestuje na NaN
  • Number.isNaN(value) zkontroluje, zda jeho argument patří do number type, a pokud ano, otestuje, zda je NaN
  • isFinite(value) převede svůj argument na číslo a poté jej otestuje, zda není NaN/Infinity/-Infinity
  • Number.isFinite(value) zkontroluje, zda jeho argument patří do number zadejte, a pokud ano, otestuje jej, zda není NaN/Infinity/-Infinity

Pro převod hodnot jako 12pt a 100px na číslo:

  • Použijte parseInt/parseFloat pro „měkký“ převod, který přečte číslo z řetězce a poté vrátí hodnotu, kterou mohli přečíst před chybou.

Pro zlomky:

  • Zaokrouhlete pomocí Math.floor , Math.ceil , Math.trunc , Math.round nebo num.toFixed(precision) .
  • Nezapomeňte, že při práci se zlomky dochází ke ztrátě přesnosti.

Další matematické funkce:

  • Zobrazte objekt Math, když je potřebujete. Knihovna je velmi malá, ale dokáže pokrýt základní potřeby.