Numbers and dates

Do arithmetic that survives money and dates, and format results for people.

05of 32
≈ 11Min read
7Parts

One number type

Every number in JavaScript is a 64-bit float. There is no separate integer type, so 5 and 5.0 are the same value, and integer arithmetic is exact only up to 2^53 − 1 (Number.MAX_SAFE_INTEGER).

Beyond that you need BigInt: 9007199254740993n. You will meet it with database ids and timestamps in nanoseconds, and almost nowhere else.

Floating point

the one everyone meets
0.1 + 0.2              // 0.30000000000000004
0.1 + 0.2 === 0.3      // false
(0.1 + 0.2).toFixed(2) // "0.30"

// comparing floats
const close = (a, b, eps = 1e-9) => Math.abs(a - b) < eps;
Money

Never store money as a float. Work in integer minor units — kopeks, cents, qəpik — and divide only when you format. Every currency bug you will ever see starts with a float.

Parsing

what each one does with awkward input
Number("12.5")     // 12.5
Number("12px")     // NaN
Number("")         // 0        ← the trap
Number(" 12 ")     // 12       — whitespace is fine
Number(null)       // 0
Number(undefined)  // NaN

parseInt("12px", 10)    // 12
parseInt("px12", 10)    // NaN
parseFloat("1.5rem")    // 1.5

Number.isFinite(x)      // a real, usable number
Number.isInteger(x)
Number.isNaN(x)         // not the global isNaN, which coerces

Rounding

toFixed rounds the binary value, so a number that looks like exactly 2.345 may be stored as 2.34499999…, and you get "2.34". If the rounding must be exact, work in integers.

five different answers
Math.round(2.5)    // 3    — .5 always goes up
Math.round(-2.5)   // -2   — which means "up" is towards +∞
Math.floor(2.9)    // 2
Math.ceil(2.1)     // 3
Math.trunc(-2.9)   // -2   — just drops the fraction
Math.floor(-2.9)   // -3

// to 2 decimal places, as a number
Math.round(2.345 * 100) / 100   // 2.35  (usually — see below)
(2.345).toFixed(2)              // "2.35" as a string, sometimes "2.34"

Math

the ones you will use
Math.min(3, 7)      Math.max(3, 7)
Math.abs(-4)        Math.sign(-4)     // 4, -1
Math.pow(2, 10)     2 ** 10           // 1024
Math.sqrt(16)       Math.hypot(3, 4)  // 4, 5
Math.random()                         // 0 ≤ n < 1

const clamp = (n, lo, hi) => Math.min(hi, Math.max(lo, n));
const lerp  = (a, b, t) => a + (b - a) * t;
const randInt = (lo, hi) => lo + Math.floor(Math.random() * (hi - lo + 1));
Random is not secure

Math.random() is predictable. For anything security-related use crypto.getRandomValues() or crypto.randomUUID().

Dates

  • Months are zero-based. Days of the month are not. This is the single commonest date bug.
  • A bare "2026-09-21" is parsed as UTC; "2026-09-21T00:00" is parsed as local time. That one character moves a date by a day.
  • Date arithmetic across months and daylight saving is genuinely hard. For anything beyond "add a day", use Temporal where it is available, or a library.
the parts that bite
new Date("2026-09-21")       // ISO — parsed as UTC midnight
new Date("21/09/2026")       // unreliable; do not do this
new Date(2026, 8, 21)        // month is ZERO-based: 8 is September
Date.now()                   // milliseconds since 1970

const d = new Date();
d.getFullYear(); d.getMonth(); d.getDate();   // getDate is day of month
d.getDay();                                   // day of week, 0 = Sunday
d.toISOString();                               // "2026-09-21T09:00:00.000Z"

Formatting for people

Separators, decimal marks, currency position and month names all differ by locale. Intl knows every one of them and is built into the browser.

Intl, never string concatenation
new Intl.NumberFormat("az").format(1234.5);
// "1.234,5"

new Intl.NumberFormat("az", { style: "currency", currency: "AZN" }).format(1234.5);
// "1.234,50 ₼"

new Intl.NumberFormat("en", { maximumFractionDigits: 1, notation: "compact" }).format(12400);
// "12.4K"

new Intl.DateTimeFormat("az", { dateStyle: "long" }).format(new Date());
new Intl.RelativeTimeFormat("en", { numeric: "auto" }).format(-1, "day");   // "yesterday"

Practice