Moderní tvorba webových aplikací

O webu

Parsování čísel v JavaScriptu: správně a bezpečně

Funkce pro práci s čísly parseInt, Number, NaN, doporučení a ukázky.

17 minut

Práce s čísly v JavaScriptu přináší velkou porci zábavy. Některé věci se hodí znát pro běžnou praxi. Další jsou spíš kuriosity vhodné k šikanování uchazečů o práci na pohovorech.

Většina zvláštních situací plyne z automatické typové konverse (tzv. type coercion), která často způsobuje, že číslo a řetězec „spolu komunikují“.

Při pokusu o sčítání platí, že pokud je alespoň jeden operand řetězec, dojde ke zřetězení:

Operand je hodnota nebo proměnná, na které operátor provádí svou operaci.

"1" + 2           // "12"
2 + "1"           // "21"
"1" + "2"         // "12"
1 + 2 + "3"       // "33"   (nejprve 1+2=3, pak 3+"3" → "33")
"1" + 2 + 3       // "123"  (nejprve "1"+2 → "12", pak "12"+3 → "123")

U ostatních operací (-, *, /) se řetězce převádějí na čísla, pokud to jde:

"10" - 1         // 9
"6" * "7"        // 42
"12" / 3         // 4
"foo" * 2        // NaN

Této skutečnosti je možné využívat i pro převod na čísla, kdy se řetězec vynásobí jedničkou nebo se přidá + před číslo v řetězci.

+"1" + 1       // 2

Skoro všechno je string

Reálné problémy plynoucí z míchání řetězců (string) s čísly (number) vyplývají z toho, že skoro všechno může být řetězec:

  1. Všechny hodnoty zadané do formulářů jsou řetězce. Ani číselný <input type="number> není výjimkou.

  2. URL parametry / query string jsou na tom stejně:

    const params = new URLSearchParams(window.location.search);
    const id = params.get("id");
    console.log(typeof id); // "string"
  3. I u JSONu získaného z API je běžnou praxí, že jsou čísla posílána jako řetězce.

    const data = JSON.parse('{"age":"42"}');
    console.log(typeof data.age); // "string"

Součet čísel vs. součet řetězců

Hodnota z <input type="text"> je řetězec. Operace x + 5 provede zřetězení, nikoliv sčítání.

x + y (řetězení)
Number(x) + Number(y)
+x + +y
parseInt(x, 10) + parseInt(y, 10)
parseFloat(x) + parseFloat(y)

Co s tím?

Number

Obecně typicky stačí převést takový vstup pomocí Number:

const age = Number(document.querySelector('#age')?.value ?? '')
const id = Number(new URLSearchParams(location.search).get('id') ?? '')
const data = JSON.parse('{"age":"42","items":[{"qty":"3"},{"qty":"5"}]}')
const age2 = Number(data.age)

A zde začíná další zábava. Datům od uživatele není dobré věřit. Aplikace by si měla poradit s každým vstupem.

Funkce Number je relativně přísná. Poradí si s bílými znaky okolo čísla (to může být i nezalomitelná mezera, takže Number("\u00A042") nebo Number("42\u00A0") je v pořádku). Prázdný řetězec nebo null převede na 0, jinak je výsledkem NaN (not a number):

Number(" 42 ")          // 42
Number("")              // 0
Number("   ")           // 0
Number(null)            // 0
Number(undefined)       // NaN
Number("42px")          // NaN
Number("\n\t3.14 ")     // 3.14
Number("42e4")          // 420000
Number("0xFF")          // 255
Number("0b1010")        // 10
Number("0o17")          // 15

Funkce Number převede na číslo i zápisy s exponentem (42e4). Stejně tak si podle prefixu dokáže zvolit jinou číselnou soustavu – šestnáctkovou/hexadecimální (prefix 0x), dvojkovou/binární (0b) nebo osmičkovou/oktalovou (0o).

parseFloat/parseInt

Pro trochu tolerantnější přístup k číslům jde použít parseFloat/parseInt (pro parsování čísla s desetinnou čárkou nebo celého).

Hlavní rozdíl je v tom, že tyto funkce dokáží odstranit nepořádek na konci čísla. Takže třeba v pohodě odstranit jednotky:

parseInt("42px", 10)       // 42
parseFloat("3.14em")       // 3.14
parseFloat("1.2e3ms")      // 1200
parseInt("08", 10)         // 8
parseInt("0x10", 16)       // 16
parseInt("0x10", 10)       // 0

Další specialita je možnost zvolit číselnou soustavu, ve které se má číslo parsovat. To je považováno za doporučený postup, protože bez jejího uvedení se ji může prohlížeč pokusit hádat. Zvlášť historicky to způsobovalo nekonsistentní situace.

parseInt("1010", 2)    // 10
parseInt("ff", 16)     // 255
parseInt("z", 36)      // 35
parseInt("08")         // 8 nebo 0 (historicky), proto vždy parseInt(s, 10)
Number(x)
+x
parseInt(x, 10)
parseFloat(x)
Number.isFinite(Number(x))
Number.isNaN(Number(x))

Číselná soustava (radix)

Radix je základ číselné soustavy. parseInt(text, radix) říká, v jaké soustavě se má řetězec číst. 10 je desítková, 2 binární, 16 šestnáctková; povolený rozsah je 2…36. Znaky az představují hodnoty 10 až 35 a nerozlišují velikost písmen. Funkce čte zleva a zastaví se na prvním nepovoleném znaku.

parseInt("1010", 2)     // 10
parseInt("ff", 16)      // 255
(255).toString(16)      // "ff"
parseInt("08", 10)      // 8
parseInt(s, base)
platné znaky
n.toString(base)

Validace čísel

Z různých specifik čísel jako je možnost zadat exponent nebo použít různé číselné soustavy plyne, že prostá validace, jestli je vstup od uživatele číslo, nemusí být dostatečná.

Je tak potřeba zvolit pro konkrétní případ, jestli přijímat zápisy s exponentem, zápisy v jiné číselné soustavě nebo třeba nekonečno Infinity.

K úvaze je i použití serialisace místo validace, kdy se neplatné znaky ve vstupu ignorují a algoritmus se snaží pochopit, co chtěl člověk zadat.

Zde může být typicky problém s oddělovači tisíců nebo desetinných míst.

Lokalisovaná čísla: čárka a mezery

JavaScript parsuje desetinnou tečku. Vstup jako "1 234,56" je potřeba převést na "1234.56". Zároveň je vhodné odstranit různé druhy mezer v tisících (NBSP, narrow NBSP, běžná mezera).

function parseCzDecimal(input) {
  const raw = String(input)
  const withoutSpaces = raw.replace(/[\u00A0\u202F\s]/g, "")
  const unified = withoutSpaces.replace(",", ".")
  return toNumberStrict(unified)
}

parseCzDecimal("1 234,56")   // 1234.56
parseCzDecimal("12,0")       // 12
parseCzDecimal("12 345")     // 12345

Udělat políčko tolerantní k českému číslu by šlo tímto způsobem.

Bez mezer
Tečka místo čárky
Number(...)
Validní tvar

K úvaze je, jestli není validace až moc přísná, že si neporadí naopak s anglickým formátem 1,234.56. Je potřeba to dobře vyzkoušet pro konkrétní případ.

Okrajové hodnoty a další typy

  • Number("") → 0, Number(null) → 0, Number(undefined) → NaN.
  • parseInt/parseFloat: parseInt(""), parseInt(null), parseInt(undefined) → NaN (protože "", "null", "undefined" nejsou čísla).
  • +x: chová se jako Number(x).
  • Boolean("") → false, Boolean(null) → false, Boolean(undefined) → false.
  • Boolean: Number(true) → 1, Number(false) → 0.
  • Pole: [] → "" → 0, [1] → "1" → 1, [1,2] → "1,2" → NaN.
  • Objekt: {} → "[object Object]" → NaN.
  • Symbol: Number(Symbol()) a parseInt(Symbol()) vyhodí TypeError.
  • BigInt: Number(10n) → 10, pozor na přesnost u velkých hodnot.
typeof x
String(x)
Number(x)
+x
parseInt(x, 10)
parseFloat(x)
Boolean(x)

Doporučení

  • Chcete‑li zvalidovat, že celý vstup je číslo, použijte Number a Number.isFinite.
  • Potřebujete‑li číslo vyčíst z počátku textu (např. "42px"), použijte parseInt(x, 10) nebo parseFloat.
  • Vždy předejte radix (číselnou soustavu) do parseInt: parseInt(s, 10).
  • Pro lokální formáty (1 234,56) nejprve vstup normalisujte (mezery, NBSP, čárka → tečka) a pak použijte přísnou konversi.

Související články

Detekce otevření DevTools

Jak zjistit, že se na stránce otevřely vývojářské nástroje.

13 minut

JavaScript null a undefined

Rozdíly mezi null a undefined v JavaScriptu, kdy je používat a jak se vyhnout běžným chybám.

12 minut

Sleep v JavaScriptu

Jak implementovat sleep/delay funkcionalitu v JavaScriptu pomocí Promise a async/await

6 minut

JavaScript Battery API

Jak v JS zjistit stav baterie, co dnes funguje a kdy API nepoužívat.

3 minuty

Web jecas.cz píše Bohumil Jahoda, kontakt
Seznam všech článků
2013–2025