JavaScript has a single numeric type, number, that holds both integers and decimals — 42 and 3.14 are the same kind of value, with no separate int to convert between. Arithmetic uses the operators you already know (+ - * /), plus % for remainder and ** for exponent.
10 + 3 // 13
10 - 4 // 6
6 * 7 // 42
20 / 4 // 5
2 ** 3 // 8
17 % 5 // 2
Multiplication and division bind tighter than addition, so 10 + 3 * 2 evaluates to 16, not 26. When you need a different order, parentheses state it explicitly: (10 + 3) * 2 is 26. One detail that surprises people arriving from other languages: / is always real division, so 7 / 2 gives 3.5, never a truncated 3.
flowchart TD A["Number literal"] --> B["One type for<br/>integers and decimals"] B --> C["e.g. 42 or 3.14"] style C fill:#e0900b,color:#fff
Remainder: the % operator's real job
% is the remainder operator, and learners underuse it because "remainder" sounds like a dead end. It is the tool for anything rhythmic or repeating. Want to know whether a number is even? n % 2 === 0. Need to cycle through a fixed set of choices? i % choices.length wraps any counter back to 0 the moment it runs off the end. Dividing a quantity and unsure where the odd unit lands? The remainder tells you.
A trap with negatives: -7 % 3 is -1 here, not 2, because the sign follows the dividend. When you need a result that is always non-negative, add the modulus and take % once more.
The Math object
Numbers carry no rounding methods of their own, so you reach for the built-in Math object. It holds constants like Math.PI and a set of functions that take a number and hand one back:
Math.round(2.7) // 3, nearest
Math.floor(2.7) // 2, always down
Math.ceil(2.1) // 3, always up
Math.max(3, 9, 1) // 9
Math.min(3, 9, 1) // 3
Math.sqrt(16) // 4
Math.abs(-7) // 7
The three rounding helpers look alike but disagree on ties and direction. round goes to the nearest integer; floor always moves toward the smaller number; ceil always moves toward the larger. With negatives that gap is sharp — Math.floor(-2.5) is -3, while Math.round(-2.5) is -2.
flowchart TD A["2.7"] --> R["round gives 3"] B["2.1"] --> C["ceil gives 3"] D["2.9"] --> F["floor gives 2"] style R fill:#1e7f3d,color:#fff style C fill:#b45309,color:#fff style F fill:#3776ab,color:#fff
console.log(10 + 3 * 2); console.log(Math.round(2.7)); console.log(Math.floor(2.7)); console.log(Math.max(3, 9, 1)); console.log(0.1 + 0.2);
When a number is not a number
Not everything that looks numeric parses into one. Number("42") gives 42, but Number("42px") gives NaN, a special value whose name stands for "not a number". It is JavaScript's way of saying "I tried to do math and the result has no value", and it appears from 0 / 0, failed parses, and Math.sqrt(-1).
The sharp edge is that NaN never equals itself, so NaN === NaN is false. Test for it with Number.isNaN(value) rather than a comparison. For forgiving input, parseInt("42px") stops at the first non-digit and returns 42, which is perfect for stripping a unit like "300px" off a CSS value.
console.log(Number("42"));
console.log(parseInt("42px"));
console.log(Number("42px"));
console.log(typeof NaN);
console.log(NaN === NaN);
Random numbers and ranges
Math.random() returns a decimal from 0 (inclusive) up to 1 (exclusive), which is rarely useful on its own. To roll an integer in a range, scale and floor: Math.floor(Math.random() * 6) + 1 simulates a die. Multiplying by 6 stretches the 0–1 span to 0 up to just under 6, flooring drops the fraction, and adding 1 shifts the result into 1–6.
Always pair Math.random with Math.floor, not Math.round. Rounding would make the highest and lowest outcomes only half as likely as the middle ones, because the edge values cover just a half-wide slice each. Floor keeps every integer equally likely.
Safe money: count cents, not dollars
The floating-point gap is tiny, but it breaks exact comparisons, which is the one thing money cannot tolerate. The fix predates JavaScript: store currency as whole cents and do all arithmetic in integers, converting back to dollars only when you display.
const priceCents = 1095; // $10.95
const paidCents = 2000; // $20.00
const changeCents = paidCents - priceCents; // 905, exact
Because 905 is an integer, the subtraction is exact and === comparisons hold. When you must show dollars, divide and format: (changeCents / 100).toFixed(2) produces "9.05". Rounding once, at the end, with Math.round is the other safe pattern — the one you will use in the addMoney exercise below.
const priceCents = 1095;
const paidCents = 2000;
const changeCents = paidCents - priceCents;
console.log(changeCents);
console.log(`$${(changeCents / 100).toFixed(2)}`);
flowchart LR A["0.1 in decimal"] --> B["stored in binary<br/>with a tiny error"] C["0.2 in decimal"] --> B B --> D["sum = 0.30000000000000004"] D --> E["so === 0.3 is false"] style D fill:#b91c1c,color:#fff style E fill:#b91c1c,color:#fff
What does this print? Decimals are stored in binary, so this sum is not exactly 0.3.
console.log(0.1 + 0.2);
0.1 and 0.2 cannot be stored exactly in binary.
The result carries a tiny error out at the far decimal places.
Write clamp(n, lo, hi) that forces n inside the range [lo, hi]. Below lo it returns lo; above hi it returns hi; otherwise it returns n. Build it from Math.min and Math.max.
function clamp(n, lo, hi) {
// return n, but no smaller than lo and no larger than hi
}
First lift n up to lo with Math.max(n, lo).
Then cap that result at hi with Math.min.
function clamp(n, lo, hi) {
return Math.min(Math.max(n, lo), hi);
}
Write addMoney(a, b) that returns the sum of two money amounts rounded to 2 decimal places, so floating-point error can never leak through. (Hint: scale by 100, round, then scale back.)
function addMoney(a, b) {
// return the sum, rounded to 2 decimals
}
Multiply the sum by 100, round with Math.round, then divide by 100.
This keeps results like 0.3 exact so === comparisons hold.
function addMoney(a, b) {
return Math.round((a + b) * 100) / 100;
}
This should return the average of three numbers, but it returns the wrong value: c alone is divided by 3, because / binds tighter than +. Fix the grouping so the whole sum is what gets divided.
function average(a, b, c) {
return a + b + c / 3;
}
Division happens before addition, so c / 3 runs first on its own.
Wrap a + b + c in parentheses so the sum is what gets divided.
function average(a, b, c) {
return (a + b + c) / 3;
}
What does this print? Math.round sends halves upward, and Math.floor always heads toward the smaller integer — watch the signs.
console.log(Math.round(2.5)); console.log(Math.round(-2.5)); console.log(Math.floor(-2.5));
Math.round(2.5) rounds the half up to 3.
For -2.5, rounding 'up' means toward +Infinity; floor always goes more negative.
Recap
- One number type covers integers and decimals;
/always divides for real, never truncating. *,/, and%bind tighter than+and-; use parentheses to override.- Reach for
Mathto round (round/floor/ceil), compare (max/min), and more. NaNsignals failed math; it never equals itself, so test withNumber.isNaN.- Decimals live in binary, so compare money in whole cents, or round right before you compare.
Next you'll meet objects — the way JavaScript groups related values under one name.