9. března 2025

Čísla

Moderní JavaScript obsahuje dva druhy čísel:

  1. Běžná čísla v JavaScriptu jsou uložena v 64-bitovém formátu IEEE-754. Jsou známa také jako „čísla s pohyblivou řádovou čárkou s dvojnásobnou přesností“. To jsou čísla, která používáme ve většině případů a v této kapitole o nich budeme hovořit.

  2. Čísla typu BigInt představují celá čísla libovolné délky. Jsou někdy zapotřebí, neboť běžné celé číslo nemůže bezpečně překročit (253-1) nebo být menší než -(253-1), jak jsme uvedli již dříve v kapitole Datové typy. Jelikož biginty se používají jen v některých speciálních oblastech, věnujeme jim zvláštní kapitolu Typ BigInt.

Zde tedy budeme hovořit o běžných číslech. Rozšiřme si své znalosti o nich.

Další způsoby, jak napsat číslo

Představme si, že musíme napsat 1 miliardu. Obvyklý způsob je:

let miliarda = 1000000000;

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

let miliarda = 1_000_000_000;

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

V reálném životě se však snažíme vyhnout zápisu dlouhých sekvencí nul. Jsme na to příliš líní. Snažíme se zapsat miliardu nějak jako "1 mld." nebo 7 miliard 300 miliónů jako "7,3 mld.". To platí pro většinu velkých čísel.

V JavaScriptu můžeme číslo zkrátit tím, že za ně přidáme písmeno "e" a uvedeme počet nul:

let miliarda = 1e9;  // 1 miliarda, doslova: 1 a 9 nul

alert( 7.3e9 );  // 7.3 miliard (totéž jako 7300000000 nebo 7_300_000_000)

Jinými slovy, e toto číslo násobí číslem 1 se zadaným počtem nul.

1e3 === 1 * 1000 // e3 znamená *1000
1.23e6 === 1.23 * 1000000 // e6 znamená *1000000

Nyní zapišme nějaké velmi malé číslo. Třeba 1 mikrosekundu (jednu milióntinu sekundy):

let mcs = 0.000001;

Stejně jako předtím nám může pomoci použití "e". Jestliže se chceme vyhnout explicitnímu zápisu nul, můžeme zapsat totéž jako:

let mcs = 1e-6; // pět nul nalevo od 1

Spočítáme-li nuly v čísle 0.000001, bude jich 6. Je to tedy přirozeně 1e-6.

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

// -3 znamená dělení číslem 1 se 3 nulami
1e-3 === 1 / 1000; // 0.001

// -6 znamená dělení číslem 1 se 6 nulami
1.23e-6 === 1.23 / 1000000; // 0.00000123

// příklad s větším číslem
1234e-2 === 1234 / 100; // 12.34, desetinná čárka se posune 2krát

Hexadecimální, binární a oktální čísla

Hexadecimální čísla (v šestnáctkové soustavě) se v JavaScriptu široce používají k reprezentaci barev, kódování znaků a mnoha dalším věcem. Pochopitelně tedy existuje kratší způsob, jak je zapsat: 0x a pak číslo.

Například:

alert( 0xff ); // 255
alert( 0xFF ); // 255 (totéž, malá a velká písmena se nerozlišují)

Binární (dvojková) a oktální (osmičková) soustava se používají jen vzácně, ale jsou také podporovány, a to za použití prefixů 0b a 0o:

let a = 0b11111111; // binární podoba 255
let b = 0o377; // oktální podoba 255

alert( a == b ); // true, stejné číslo 255 na obou stranách

Takovou podporu mají pouze tři číselné soustavy. Pro jiné číselné soustavy bychom měli použít funkci parseInt (kterou uvidíme později v této kapitole).

toString(základ)

Metoda číslo.toString(základ) vrátí řetězcovou reprezentaci čísla číslo v číselné soustavě o zadaném základu základ.

Příklad:

let číslo = 255;

alert( číslo.toString(16) );  // ff
alert( číslo.toString(2) );   // 11111111

Hodnota základ může být od 2 do 36. Standardně je to 10.

Běžná použití jsou:

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

  • základ=2 slouží zejména pro ladění bitových operací, číslice mohou být 0 nebo 1.

  • základ=36 je maximum, číslice mohou být 0..9 nebo A..Z. K reprezentaci čísla se používá celá latinská abeceda. Legrační, ale užitečné využití 36 představuje případ, kdy potřebujeme změnit dlouhý číselný identifikátor na něco kratšího, například abychom vytvořili kratší URL. Můžeme jej snadno reprezentovat v číselné soustavě o základu 36:

    alert( 123456..toString(36) ); // 2n9c
Volání metody dvěma tečkami

Prosíme všimněte si, že dvě tečky v 123456..toString(36) není překlep. Chceme-li volat metodu přímo na čísle, např. toString v uvedeném příkladu, pak za číslo musíme umístit dvě tečky ...

Kdybychom umístili jedinou tečku: 123456.toString(36), nastala by chyba, protože syntaxe JavaScriptu očekává za první tečkou desetinnou část. Když však uvedeme další tečku, JavaScript pozná, že desetinná část je prázdná, a nyní použije metodu.

Zaokrouhlování

Jedna z nejčastěji používaných operací při práci s čísly je zaokrouhlování.

Pro zaokrouhlování existuje několik vestavěných funkcí:

Math.floor
Zaokrouhluje dolů: 3.1 se zaokrouhlí na 3, -1.1 se zaokrouhlí na -2.
Math.ceil
Zaokrouhluje nahoru: 3.1 se zaokrouhlí na 4, -1.1 se zaokrouhlí na -1.
Math.round
Zaokrouhluje na nejbližší celé číslo: 3.1 se zaokrouhlí na 3, 3.6 se zaokrouhlí na 4. V prostředních případech se 3.5 zaokrouhlí nahoru na 4 a -3.5 se zaokrouhlí nahoru na -3.
Math.trunc (není podporována v Internet Exploreru)
Odstraní vše za desetinnou čárkou bez zaokrouhlení: 3.1 se převede na 3, -1.1 se převede na -1.

Rozdíly mezi těmito funkcemi shrnuje následující tabulka:

Math.floor Math.ceil Math.round Math.trunc
3.1 3 4 3 3
3.5 3 4 4 3
3.6 3 4 4 3
-1.1 -2 -1 -1 -1
-1.5 -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ž chceme zaokrouhlit číslo na n-tou číslici za desetinnou čárkou?

Máme například 1.2345 a chceme toto číslo zaokrouhlit na 2 desetinná místa, abychom dostali jen 1.23.

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

  1. Násobit a dělit.

    Abychom například zaokrouhlili číslo na 2. číslici za desetinnou čárkou, můžeme toto číslo vynásobit 100, zavolat zaokrouhlovací funkci a pak je znovu vydělit.

    let číslo = 1.23456;
    
    alert( Math.round(číslo * 100) / 100 ); // 1.23456 -> 123.456 -> 123 -> 1.23
  2. Metoda toFixed(n) zaokrouhluje číslo na n číslic za desetinnou čárkou a vrací řetězcovou reprezentaci výsledku.

    let číslo = 12.34;
    alert( číslo.toFixed(1) ); // "12.3"

    Zaokrouhlí číslo nahoru nebo dolů na nejbližší hodnotu, podobně jako Math.round:

    let číslo = 12.36;
    alert( číslo.toFixed(1) ); // "12.4"

    Všimněte si, že výsledkem toFixed je řetězec. Je-li desetinná část čísla kratší, než bylo vyžadováno, na konec se přidají nuly:

    let číslo = 12.34;
    alert( číslo.toFixed(5) ); // "12.34000", přidají se nuly, aby číslic bylo přesně 5

    Můžeme jej převést na číslo pomocí unárního plus nebo volání Number(), např. napsat +číslo.toFixed(5).

Nepřesné výpočty

Číslo je vnitřně reprezentováno v 64-bitovém formátu IEEE-754, takže se ukládá přesně do 64 bitů: 52 z nich se používá k uložení číslic, v 11 z nich je uložena pozice desetinné čárky a 1 bit je pro znaménko.

Je-li číslo opravdu velké, může toto 64-bitové úložiště překročit a stát se speciální číselnou hodnotou Infinity (nekonečno):

alert( 1e500 ); // Infinity

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

Uvažujme tento (nepravdivý!) test rovnosti:

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

Je to tak. Jestliže ověříme, zda součet 0.1 a 0.2 je 0.3, dostaneme false.

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

alert( 0.1 + 0.2 ); // 0.30000000000000004

Ouvej! Představme si, že vytváříme elektronický obchod a návštěvník si do košíku uloží zboží za $0.10 a za $0.20. Celková cena pak bude $0.30000000000000004. To každého překvapí.

Ale proč se to děje?

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

alert(0.1.toString(2)); // 0.0001100110011001100110011001100110011001100110011001101
alert(0.2.toString(2)); // 0.001100110011001100110011001100110011001100110011001101
alert((0.1 + 0.2).toString(2)); // 0.0100110011001100110011001100110011001100110011001101

Co je vlastně 0.1? Je to jedna děleno deseti 1/10, jedna desetina. V desítkové soustavě lze taková čísla snadno reprezentovat. Srovnejme si to s jednou třetinou: 1/3. Z ní se stane nekonečné desetinné číslo 0.33333(3).

Je tedy zaručeno, že dělení mocninami 10 bude v desítkové soustavě fungovat dobře, ale dělení třemi ne. Ze stejného důvodu je v binární soustavě zaručeno, že bude fungovat dělení mocninami 2, ale z 1/10 se stane nekonečné binární číslo.

V binární soustavě prostě neexistuje způsob, jak uložit přesně 0,1 nebo přesně 0,2, stejně jako v desítkové soustavě není způsob, jak uložit jako desetinné číslo jednu třetinu.

Číselný formát IEEE-754 to řeší zaokrouhlením na nejbližší možné číslo. Tato zaokrouhlovací pravidla nám běžně neumožňují vidět tuto „drobnou ztrátu přesnosti“, ale ta tam je.

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.

To není jen JavaScript

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

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

Můžeme se tomuto problému vyhnout? Jistě. Nejspolehlivější metoda je zaokrouhlit výsledek pomocí metody toFixed(n):

let součet = 0.1 + 0.2;
alert( součet.toFixed(2) ); // "0.30"

Prosíme všimněte si, že toFixed vrací vždy řetězec. Zajišťuje, že za desetinnou tečkou má vždy 2 číslice. To se obzvláště hodí, když máme elektronický obchod a potřebujeme zobrazit $0.30. V jiných případech můžeme použít unární plus, abychom řetězec převedli na číslo:

let součet = 0.1 + 0.2;
alert( +součet.toFixed(2) ); // 0.3

Můžeme také dočasně násobit tato čísla 100 (nebo vyšším číslem), abychom z nich vytvořili celá čísla, provést výpočty a pak je znovu vydělit. Pak, jelikož provádíme výpočty s celými čísly, chyba se trochu sníží, ale při dělení ji stále dostaneme:

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

Použití násobení a dělení tedy chybu zredukuje, ale úplně ji neodstraní.

Někdy se snažíme úplně se desetinným číslům vyhnout. Například když vytváříme obchod, můžeme ukládat ceny v centech namísto v dolarech. Ale co když aplikujeme slevu 30%? V praxi je úplné vyhnutí se desetinným číslům možné jen zřídka. Prostě je zaokrouhlujte, abyste odřízli „zbytky“, když je třeba.

Legrační věcička

Zkuste si spustit tohle:

// Ahoj! Já jsem samozvyšující se číslo!
alert( 9999999999999999 ); // zobrazí 10000000000000000

To trpí stejným neduhem: ztrátou přesnosti. Pro číslo je 64 bitů, 52 z nich lze použít k uložení číslic, ale to nestačí. Nejméně významné číslice tedy zmizí.

JavaScript při takových událostech nevyvolá chybu. Udělá, co může, aby se číslo vešlo do požadovaného formátu, ale naneštěstí tento formát není dostatečně velký.

Dvě nuly

Dalším legračním důsledkem této interní reprezentace čísel je existence dvou nul: 0 a -0.

Je to proto, že znaménko je reprezentováno jediným bitem, který může být nastaven na 1 nebo 0 pro jakékoli číslo včetně nuly.

Ve většině případů je tento rozdíl neznatelný, protože operátory jsou vytvořeny tak, aby s oběma nulami zacházely stejně.

Testy: isFinite a isNaN

Vzpomínáte si na tyto dvě speciální číselné hodnoty?

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

Patří k typu number, ale nejsou to „normální“ čísla, takže existují speciální funkce, které je prověří:

  • isNaN(hodnota) převede svůj argument na číslo a pak jej otestuje, zda je NaN:

    alert( isNaN(NaN) ); // true
    alert( isNaN("řetězec") ); // true

    Ale potřebujeme vůbec tuto funkci? Nemůžeme jednoduše použít porovnání === NaN? Bohužel ne. Hodnota NaN je unikátem, který se nerovná ničemu jinému, dokonce ani sám sobě:

    alert( NaN === NaN ); // false
  • isFinite(hodnota) převede svůj argument na číslo a vrátí true, jestliže je to skutečné číslo a ne NaN/Infinity/-Infinity:

    alert( isFinite("15") ); // true
    alert( isFinite("řetězec") ); // false, protože je to speciální hodnota: NaN
    alert( isFinite(Infinity) ); // false, protože je to speciální hodnota: Infinity

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

let číslo = +prompt("Zadejte číslo", '');

// bude true, pokud nezadáte Infinity, -Infinity nebo něco jiného než číslo
alert( isFinite(číslo) );

Prosíme všimněte si, že s prázdným řetězcem nebo s řetězcem složeným pouze z mezer se zachází jako s 0 ve všech číselných funkcích včetně isFinite.

Number.isNaN a Number.isFinite

Metody Number.isNaN a Number.isFinite jsou „striktnější“ verze funkcí isNaN a isFinite. Svůj argument nepřevádějí automaticky na číslo, ale místo toho ověří, zda jejich argument je typu number.

  • Number.isNaN(hodnota) vrací true, jestliže argument je typu number a je NaN. V jakémkoli jiném případě vrací false.

    alert( Number.isNaN(NaN) ); // true
    alert( Number.isNaN("řetězec" / 2) ); // true
    
    // Všimněte si rozdílu:
    alert( Number.isNaN("řetězec") ); // false, protože "řetězec" je typu string, ne typu number
    alert( isNaN("řetězec") ); // true, protože isNaN převede "řetězec" na číslo a jako výsledek této konverze získá NaN
  • Number.isFinite(value) vrací true, jestliže argument je typu number a není NaN/Infinity/-Infinity. V jakémkoli jiném případě vrací false.

    alert( Number.isFinite(123) ); // true
    alert( Number.isFinite(Infinity) ); // false
    alert( Number.isFinite(2 / 0) ); // false
    
    // Všimněte si rozdílu:
    alert( Number.isFinite("123") ); // false, protože "123" je typu string, ne typu number
    alert( isFinite("123") ); // true, protože isFinite převede řetězec "123" na číslo 123

Svým způsobem jsou Number.isNaN a Number.isFinite jednodušší a přímější než funkce isNaN a isFinite. V praxi se však většinou používají isNaN a isFinite, jelikož jsou kratší na napsání.

Porovnání pomocí Object.is

Existuje speciální vestavěná metoda Object.is, která porovnává hodnoty stejně jako ===, ale ve dvou krajních případech je spolehlivější:

  1. Funguje pro NaN: Object.is(NaN, NaN) === true, což je dobrá věc.
  2. Hodnoty 0 a -0 jsou rozdílné: Object.is(0, -0) === false, technicky je to správně, protože vnitřně číslo obsahuje znaménkový bit, který se může lišit, i když jsou všechny ostatní bity nulové.

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

Metodu Object.is zde zmiňujeme proto, že se často používá ve specifikaci JavaScriptu. Když interní algoritmus potřebuje porovnat, zda jsou dvě hodnoty přesně stejné, používá Object.is (interně nazvanou SameValue).

parseInt a parseFloat

Číselná konverze prováděná pomocí plus + nebo Number() je striktní. Není-li hodnota přesně číslo, konverze selže:

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

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

V reálném životě však často máme hodnoty s jednotkami, např. "100px" nebo "12pt" v CSS. Navíc v mnoha zemích se symbol měny píše až za částku, takže máme "19€" a rádi bychom z toho získali číselnou hodnotu.

K tomu slouží funkce parseInt a parseFloat.

„Načítají“ číslo z řetězce tak dlouho, dokud to jde. Jakmile nastane chyba, vrátí nahromaděné číslo. Funkce parseInt vrátí celé číslo, zatímco parseFloat vrátí číslo s pohyblivou řádovou čárkou:

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

alert( parseInt('12.3') ); // 12, vrátí se jen celá část
alert( parseFloat('12.3.4') ); // 12.3, druhá tečka ukončí načítání

Existují situace, v nichž parseInt/parseFloat vrátí NaN. To se stane tehdy, když nelze načíst ani jednu číslici:

alert( parseInt('a123') ); // NaN, první znak tento proces zastaví
Druhý argument parseInt(řetězec, soustava)

Funkce parseInt() má nepovinný druhý parametr. Ten specifikuje základ číselné soustavy, takže parseInt může také načítat řetězce s hexadecimálními čísly, binárními čísly a podobně:

alert( parseInt('0xff', 16) ); // 255
alert( parseInt('ff', 16) ); // 255, funguje to i bez 0x

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() ); // ... (jakákoli náhodná čísla)
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, exponent)

Vrátí n umocněné na zadaný exponent.

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

V objektu Math jsou i další konstanty a funkce včetně goniometrických. Můžete je najít v dokumentaci k objektu Math.

Shrnutí

Abychom napsali číslo s mnoha nulami:

  • Připojíme za číslo "e" a počet nul. Například 123e6 je totéž jako 123 s 6 nulami: 123000000.
  • Záporné číslo za "e" způsobí, že číslo bude děleno číslem 1 se zadaným počtem nul. Například 123e-6 znamená 0.000123 (123 milióntin).

Pro různé číselné soustavy:

  • Můžeme zapisovat čísla přímo v hexadecimální (0x), oktální (0o) a binární (0b) soustavě.
  • parseInt(řetězec, základ) převede řetězec na celé číslo v číselné soustavě o zadaném základu základ, 2 ≤ základ ≤ 36.
  • číslo.toString(základ) převede číslo na řetězec v číselné soustavě o zadaném základu základ.

Pro testování čísel:

  • isNaN(hodnota) převede svůj argument na číslo a pak testuje, zda je NaN
  • Number.isNaN(hodnota) ověří, zda je její argument typu number, a pokud ano, testuje, zda je NaN
  • isFinite(hodnota) převede svůj argument na číslo a pak testuje, zda není NaN/Infinity/-Infinity
  • Number.isFinite(hodnota) ověří, zda je její argument typu number, a pokud ano, testuje, zda není NaN/Infinity/-Infinity

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

  • Pro „měkkou“ konverzi používejte parseInt/parseFloat, která načte číslo z řetězce a pak vrátí hodnotu, kterou dokázala přečíst, než nastala chyba.

Pro desetinná čísla:

  • Zaokrouhlujte pomocí Math.floor, Math.ceil, Math.trunc, Math.round nebo číslo.toFixed(přesnost).
  • Při práci s desetinnými čísly se ujistěte, že nezapomínáte na ztrátu přesnosti.

Další matematické funkce:

  • Až je budete potřebovat, podívejte se na objekt Math. Je to velmi malá knihovna, ale základní potřeby dokáže pokrýt.

Úlohy

důležitost: 5

Vytvořte skript, který vyzve návštěvníka, aby zadal dvě čísla, a pak zobrazí jejich součet.

Spustit demo

P.S. Je tam chyták s typy.

let a = +prompt("První číslo?", "");
let b = +prompt("Druhé číslo?", "");

alert( a + b );

Všimněte si unárního plus + před prompt, které okamžitě konvertuje hodnotu na číslo.

Jinak by a a b byly řetězce a součtem by bylo jejich zřetězení, tedy: "1" + "2" = "12".

důležitost: 4

Podle dokumentace Math.round a toFixed zaokrouhlují obě na nejbližší číslo: 0..4 se zaokrouhluje dolů, zatímco 5..9 nahoru.

Například:

alert( 1.35.toFixed(1) ); // 1.4

Proč se v podobném níže uvedeném příkladu 6.35 zaokrouhlí na 6.3 a ne na 6.4?

alert( 6.35.toFixed(1) ); // 6.3

Jak zaokrouhlit 6.35 správně?

Interně je desetinné číslo 6.35 nekonečné binární číslo. Jako vždy v takových případech je uloženo se ztrátou přesnosti.

Podívejme se:

alert( 6.35.toFixed(20) ); // 6.34999999999999964473

Ztrátou přesnosti se číslo může zvýšit i snížit. V tomto konkrétním případě se číslo o něco málo sníží, proto bude zaokrouhleno dolů.

A co 1.35?

alert( 1.35.toFixed(20) ); // 1.35000000000000008882

Zde ztráta přesnosti číslo trošičku zvýšila, takže se zaokrouhlilo nahoru.

Jak můžeme problém s 6.35 vyřešit, chceme-li, aby se zaokrouhlilo správně?

Měli bychom je před zaokrouhlením přiblížit k celému číslu:

alert( (6.35 * 10).toFixed(20) ); // 63.50000000000000000000

Všimněte si, že 63.5 nemá vůbec žádnou ztrátu přesnosti. Je to proto, že desetinná část 0.5 je ve skutečnosti 1/2. Zlomky s mocninou 2 ve jmenovateli jsou v binární soustavě reprezentovány přesně, takže je nyní můžeme zaokrouhlit:

alert( Math.round(6.35 * 10) / 10 ); // 6.35 -> 63.5 -> 64(zaokrouhleno) -> 6.4
důležitost: 5

Vytvořte funkci načtiČíslo, která se bude ptát na číslo tak dlouho, až návštěvník zadá platnou číselnou hodnotu.

Výslednou hodnotu funkce musí vrátit jako číslo.

Návštěvník může tento proces ukončit i zadáním prázdného řádku nebo stisknutím „Storno“. V takovém případě by funkce měla vrátit null.

Spustit demo

Otevřít pískoviště s testy.

function načtiČíslo() {
  let číslo;

  do {
    číslo = prompt("Zadejte číslo, prosím:", 0);
  } while ( !isFinite(číslo) );

  if (číslo === null || číslo === '') return null;

  return +číslo;
}

alert(`Načteno: ${načtiČíslo()}`);

Řešení je trochu složitější, než by mohlo být, protože si musíme poradit s null/prázdnými řádky.

Ve skutečnosti tedy přijímáme vstup tak dlouho, dokud to není „skutečné číslo“. Tuto podmínku splňují i null (storno) a prázdný řádek, protože v číselné podobě jsou obě 0.

Po skončení musíme zacházet s null a s prázdným řádkem speciálně (vrátit null), jelikož jejich konverze na číslo by vrátila 0.

Otevřít řešení s testy na pískovišti.

důležitost: 4

Tento cyklus je nekonečný. Nikdy neskončí. Proč?

let i = 0;
while (i != 10) {
  i += 0.2;
}

Protože i nikdy nebude rovno 10.

Spusťme si jej, abychom viděli skutečné hodnoty i:

let i = 0;
while (i < 11) {
  i += 0.2;
  if (i > 9.8 && i < 10.2) alert( i );
}

Žádná z nich není přesně 10.

Takové věci se dějí kvůli ztrátám přesnosti při přičítání desetinných čísel jako 0.2.

Důsledek: když pracujete s desetinnými čísly, vyvarujte se testů rovnosti.

důležitost: 2

Vestavěná funkce Math.random() vytváří náhodnou hodnotu od 0 do 1 (kromě 1).

Napište funkci random(min, max), která bude generovat náhodné číslo s pohyblivou řádovou čárkou od min do max (kromě max).

Příklady, jak má fungovat:

alert( random(1, 5) ); // 1.2345623452
alert( random(1, 5) ); // 3.7894332423
alert( random(1, 5) ); // 4.3435234525

Musíme „namapovat“ všechny hodnoty z intervalu 0…1 na hodnoty od min do max.

To můžeme udělat ve dvou krocích:

  1. Když vynásobíme náhodné číslo z 0…1 číslem max-min, pak se interval možných hodnot zvětší z 0..1 na 0..max-min.
  2. Když nyní přičteme min, možný interval se změní na interval od min do max.

Funkce:

function random(min, max) {
  return min + Math.random() * (max - min);
}

alert( random(1, 5) );
alert( random(1, 5) );
alert( random(1, 5) );
důležitost: 2

Vytvořte funkci randomInteger(min, max), která vygeneruje náhodné celé číslo od min do max, přičemž vygenerovanými hodnotami mohou být i min a max.

Každé číslo z intervalu min..max se musí objevit se stejnou pravděpodobností.

Příklady, jak má fungovat:

alert( randomInteger(1, 5) ); // 1
alert( randomInteger(1, 5) ); // 3
alert( randomInteger(1, 5) ); // 5

Jako základ můžete použít řešení z předchozí úlohy.

Jednoduché, ale nesprávné řešení

Jednoduchým, ale nesprávným řešením by bylo generovat hodnotu od min do max a zaokrouhlit ji:

function randomInteger(min, max) {
  let náhodnéČíslo = min + Math.random() * (max - min);
  return Math.round(náhodnéČíslo);
}

alert( randomInteger(1, 3) );

Tato funkce funguje, ale nekorektně. Pravděpodobnost, že získáme krajní hodnoty min a max, je dvakrát nižší, než u ostatních hodnot.

Jestliže si spustíte výše uvedený příklad mnohokrát po sobě, brzy uvidíte, že nejčastěji se objevuje 2.

Děje se to proto, že Math.round() získává náhodná čísla z intervalu 1..3 a zaokrouhluje je následovně:

hodnoty od 1    ... do 1.4999999999  se zaokrouhlí na 1
hodnoty od 1.5  ... do 2.4999999999  se zaokrouhlí na 2
hodnoty od 2.5  ... do 2.9999999999  se zaokrouhlí na 3

Nyní jasně vidíme, že 1 má dvakrát méně hodnot než 2. Totéž platí pro 3.

Správné řešení

Tato úloha má mnoho správných řešení. Jedno z nich je přizpůsobit hranice intervalu. Abychom zajistili stejné intervaly, můžeme generovat hodnoty od 0.5 do 3.5 a tím zvýšit požadované pravděpodobnosti krajních hodnot:

function randomInteger(min, max) {
  // nyní náhodnéČíslo je od (min-0.5) do (max+0.5)
  let náhodnéČíslo = min - 0.5 + Math.random() * (max - min + 1);
  return Math.round(náhodnéČíslo);
}

alert( randomInteger(1, 3) );

Alternativním způsobem by bylo použít Math.floor pro náhodné číslo od min do max+1:

function randomInteger(min, max) {
  // zde náhodnéČíslo je od min do (max+1)
  let náhodnéČíslo = min + Math.random() * (max + 1 - min);
  return Math.floor(náhodnéČíslo);
}

alert( randomInteger(1, 3) );

Nyní jsou všechny intervaly mapovány tímto způsobem:

hodnoty od 1  ... do 1.9999999999  se zaokrouhlí na 1
hodnoty od 2  ... do 2.9999999999  se zaokrouhlí na 2
hodnoty od 3  ... do 3.9999999999  se zaokrouhlí na 3

Všechny intervaly mají stejnou délku, takže konečné rozložení je rovnoměrné.

Mapa tutoriálu

Komentáře

přečtěte si před komentováním…
  • Máte-li návrhy na zlepšení, vytvořte prosím issue na GitHubu nebo pull request místo komentáře.
  • Pokud v článku něčemu nerozumíte, napište prosím, čemu přesně a na kterém místě.
  • Pro vložení několika slov kódu použijte značku <code>, pro několik řádků je obalte značkou <pre>, pro více než 10 řádků vložte odkaz na pískoviště (plnkr, jsbin, codepen…)