Skip to content

Numbers and math

docs.scrimba.com

A shopping cart adds up prices, a game tracks a score, a form works out how much tax to charge. All of it comes down to numbers and doing arithmetic with them. JavaScript treats numbers as one of its core building blocks, and this chapter is about what you can do with them: the everyday maths, the built-in helpers for rounding and randomness, and the piece that trips up almost everyone, turning text typed into a page back into a number you can add up.

Numbers and arithmetic

JavaScript has one number type, and it covers everything. Whole numbers like 42 and decimals like 9.99 are both the same kind of value, so you never pick between them:

js
const price = 9.99;
const quantity = 3;
const total = price * quantity;
console.log(total); // 29.97

You do maths with the symbols you already know: + adds, - subtracts, * multiplies, and / divides. There is one more that is less familiar, %, the remainder. It gives you what is left over after dividing:

js
const remainder = 7 % 2;
console.log(remainder); // 1

Seven divided by two is three with one left over, so 7 % 2 is 1. This is how you check whether a number is even: an even number divided by two has nothing left over, so number % 2 is 0 for even numbers and 1 for odd ones.

JavaScript has a single number type for both whole numbers and decimals, so 42 and 9.99 are the same kind of value. That is unlike languages that split integers and floats into separate types; here you never declare which one you mean.

The five arithmetic operators are +, -, *, /, and %. The remainder operator % returns what is left after division, and it earns its keep more than beginners expect:

js
const total = 17;
const perRow = 5;
const leftOver = total % perRow;
console.log(leftOver); // 2

Laying out 17 items 5 to a row leaves 2 in the last row, and 17 % 5 tells you that. The classic use is testing even or odd: count % 2 is 0 for even numbers and 1 for odd ones, which is how you alternate row colours or split a list in half.

Operators follow normal maths precedence, so *, /, and % bind tighter than + and -, and equal-precedence operators read left to right. Reach for parentheses when the order matters:

js
const withTax = (100 + 20) * 1.1;
console.log(withTax); // 132

JavaScript has one number type, a 64-bit floating-point value (the IEEE 754 double, the same representation most languages call a double). There is no separate integer type at this level, which means 42, 9.99, and -3 are all the same kind of thing, and every integer you write is really a float that happens to have no fractional part.

The five operators are + - * / %. The remainder % is the one worth pinning down, because it is remainder, not modulo: it takes the sign of the left operand, so -7 % 3 is -2, not 1. For the everyday case its value is stable and useful. Testing n % 2 === 0 for even is the canonical trick, and it holds for negatives too since -4 % 2 is still 0.

js
const total = 128;
const pageSize = 25;
const onLastPage = total % pageSize;
console.log(onLastPage); // 3

Precedence follows standard maths: *, /, and % share a tier above + and -, and within a tier evaluation is left to right (left associative). The whole grammar is more layered than that once comparisons and logic enter, which is the subject of operators. For arithmetic alone the rule you need is simple: multiplicative before additive, parenthesise anything a reader would have to pause over. Clarity beats saving three characters.

js
const average = (a + b + c) / 3;
JunoNumbers and arithmetic There is one number type in JavaScript, so whole numbers and decimals are the same thing and you never choose between them. The operators are + - * / plus %, the remainder, which is what is left after dividing. The handy trick is % 2: it is 0 for even numbers and 1 for odd ones.
JunoNumbers and arithmetic One number type covers whole numbers and decimals, so there is no int-versus-float decision to make. The five operators are + - * / and %, and % is worth remembering: count % 2 gives you even-or-odd, and it also tells you what is left in a partly filled row. Precedence is normal maths, so parenthesise anything a reader would have to think about.
JunoNumbers and arithmetic Every number is a 64-bit float, so there is no separate integer type and 42 is a double with no fraction. The one gotcha in % is that it is remainder, not modulo, so it takes the sign of the left side and -7 % 3 is -2. For the even-or-odd test that never bites you, since n % 2 === 0 holds for negatives too.

The Math object

When you need more than the basic operators, JavaScript gives you Math, a built-in collection of maths helpers. You call them by name with a dot:

js
console.log(Math.round(4.6)); // 5
console.log(Math.floor(4.6)); // 4
console.log(Math.ceil(4.1)); // 5

Math.round rounds to the nearest whole number, Math.floor always rounds down, and Math.ceil always rounds up. There are also Math.max and Math.min, which pick the biggest or smallest of the numbers you give them:

js
console.log(Math.max(3, 8, 5)); // 8
console.log(Math.min(3, 8, 5)); // 3

And Math.random, which gives a random decimal from 0 up to (but not including) 1. On its own that is rarely what you want, so the common recipe is to multiply it and floor it to get a random whole number:

js
const roll = Math.floor(Math.random() * 6) + 1;
console.log(roll); // 4 (a different number each time)

Math.random() * 6 lands somewhere from 0 up to 6, Math.floor drops it to a whole number from 0 to 5, and + 1 shifts it to a dice roll of 1 to 6.

Math is a built-in object holding maths functions and constants. You do not create it; it is always there, and you call its methods with a dot.

The rounding trio is the one you reach for most:

js
console.log(Math.round(4.5)); // 5, nearest whole number
console.log(Math.floor(4.9)); // 4, always down
console.log(Math.ceil(4.1)); // 5, always up

Math.max and Math.min return the largest and smallest of their arguments, which is handy for clamping a value into a range:

js
const clamped = Math.min(Math.max(userValue, 0), 100);
console.log(clamped); // stays between 0 and 100

Math.random returns a float in the range [0, 1), meaning 0 is possible but 1 never is. To turn that into a random whole number from 0 up to n, the pattern is Math.floor(Math.random() * n):

js
const index = Math.floor(Math.random() * 5);
console.log(index); // a whole number from 0 to 4

Multiplying spreads the range to [0, n), and flooring cuts off the decimal so you land on a whole number. Add an offset when you want a range that does not start at zero, like + 1 for a dice roll.

Math is a static namespace object: a plain container of maths methods and constants, not something you instantiate. Every method is called as Math.something(), and there is exactly one Math in the runtime.

The rounding methods each round toward a different target: Math.round to nearest (ties go up, so Math.round(2.5) is 3 but Math.round(-2.5) is -2), Math.floor toward negative infinity, Math.ceil toward positive infinity. That floor-is-not-truncation distinction matters for negatives, where Math.floor(-1.2) is -2, not -1.

js
console.log(Math.floor(-1.2)); // -2, floor goes down even for negatives

Math.max and Math.min take any count of arguments and are the clean way to clamp:

js
const clamped = Math.min(Math.max(value, min), max);

Math.random returns a uniformly distributed float in [0, 1). The idiom for a random integer is Math.floor(Math.random() * range) + start: scale by the range width, floor to an integer, shift by the start. Two cautions worth carrying: it is not cryptographically secure, so never use it for tokens or anything a user should not be able to guess (crypto.getRandomValues exists for that), and off-by-one bugs come from confusing inclusive and exclusive bounds, so decide up front whether your top value is reachable.

js
const dice = Math.floor(Math.random() * 6) + 1;
console.log(dice); // 1 through 6 inclusive
JunoThe Math objectMath is a ready-made toolbox of maths helpers you call with a dot: Math.round, Math.floor (down), Math.ceil (up), and Math.max / Math.min for the biggest and smallest. Math.random gives a decimal from 0 up to 1, and the recipe worth memorising is Math.floor(Math.random() * n) for a random whole number. It took me a few tries to trust that pattern, so copy it and read it slowly.
JunoThe Math objectMath is always there, no setup needed, and you call its methods with a dot. Keep the rounding trio straight: round to nearest, floor down, ceil up, and use min and max together to clamp a value into a range. For randomness, Math.floor(Math.random() * n) gives a whole number from 0 up to n, and an offset moves the range.
JunoThe Math objectMath is a static namespace, so there is one of it and nothing to construct. Watch the rounding targets on negatives: Math.floor(-1.2) is -2, not truncation, and Math.round breaks ties upward. Math.floor(Math.random() * range) + start is the integer idiom, but Math.random is not secure, so use crypto.getRandomValues for anything a user should not guess.

Turning strings into numbers

Anything a person types into a page arrives as text, even when it looks like a number. Type 5 into a box and you get the string "5", not the number 5. That matters because + on two strings joins them instead of adding:

js
const a = "5";
const b = "3";
console.log(a + b); // "53", not 8

To do maths, you convert the text to a number first. The clearest way is Number():

js
const count = Number("5");
console.log(count + 3); // 8

There are two more you will see: parseInt reads a whole number and drops any decimal, and parseFloat keeps the decimal:

js
console.log(parseInt("42px")); // 42
console.log(parseFloat("9.99")); // 9.99

Both of these can read a number sitting at the start of some text, like "42px", which Number cannot. There is also a shortcut, a + in front of a string, which converts it the same way Number does:

js
console.log(+"7"); // 7

Input from a page is always a string. A form field, a URL, a data attribute: all text, even "5". Since + concatenates strings, adding two text values joins them rather than summing them, which is the single most common numbers bug for beginners:

js
const price = "10";
const shipping = "5";
console.log(price + shipping); // "105", string concatenation

Convert before you calculate. Number() is the strict, readable choice: it turns a clean numeric string into a number and gives NaN for anything that is not fully numeric.

js
console.log(Number("42")); // 42
console.log(Number("42px")); // NaN, not fully numeric

parseInt and parseFloat are more forgiving. They read a number from the front of a string and stop at the first character that does not fit, which is why parseInt("42px") is 42. parseInt discards any fractional part; parseFloat keeps it:

js
console.log(parseInt("42px", 10)); // 42
console.log(parseFloat("3.14 radians")); // 3.14

Pass 10 as the second argument to parseInt so it always reads base ten. The unary + is a terse converter that behaves like Number: use Number() for whole values, parseInt/parseFloat for numbers embedded in text.

Text from the outside world is always a string, and because + is overloaded for concatenation, "10" + "5" is "105". Every arithmetic operator except + coerces its operands to numbers, so "10" * 1 is 10, but leaning on that is a readability trap. Convert deliberately.

Number() is whole-string coercion: it succeeds only if the entire trimmed string is a valid numeric literal, otherwise NaN. It understands hex (Number("0x1f") is 31), exponents, and leading or trailing whitespace, and it treats "" as 0, which is a sharp edge worth knowing.

js
console.log(Number("")); // 0, a sharp one to miss
console.log(Number("  12 ")); // 12, whitespace trimmed
console.log(Number("12abc")); // NaN

parseInt and parseFloat parse a prefix: they read as far as they can and ignore the rest, so parseInt("12abc") is 12. Always give parseInt its radix (parseInt(str, 10)); without it, an input like "0x10" is read as hex. The unary + is the shortest form of Number coercion and appears often in terse code (+"3.5" is 3.5), with the same rules and the same "" becomes 0 surprise.

The practical rule: Number (or +) when the whole value should be numeric and anything else is an error you want to catch, parseInt/parseFloat when a number is embedded in text you expect. Whichever you pick, the result can be NaN, which the next section is about. Comparison and coercion beyond this live in operators, and the string side of the story is in strings.

JunoTurning strings into numbers Anything typed into a page is text, so "5" is not the number 5, and "5" + "3" gives "53" instead of 8. Convert first: Number("5") is the clearest, parseInt reads a whole number, parseFloat keeps decimals, and a + in front of a string is a quick shortcut. Watch for the joining-instead-of-adding trap, it catches everyone once.
JunoTurning strings into numbers Page input is always a string, and since + joins strings, "10" + "5" is "105", the most common numbers bug there is. Use Number() when the whole value should be numeric, and parseInt or parseFloat when a number is sitting inside text like "42px". Give parseInt its base-ten argument and it will not surprise you.
JunoTurning strings into numbers Every operator except + coerces to number, so "10" * 1 works, but convert on purpose instead. Number() demands the whole string be numeric and quietly turns "" into 0, while parseInt and parseFloat parse a prefix and ignore the rest, so always pass parseInt a radix of 10. Any of them can hand you NaN, which is the next thing to get comfortable with.

The sharp edges of numbers

Numbers in JavaScript hold a surprise that is worth seeing once, early. Add two simple decimals and you can get a strange answer:

js
console.log(0.1 + 0.2); // 0.30000000000000004

That is not a bug in your code. Computers store decimals in a way that cannot represent every value exactly, so a tiny error creeps in. It rarely matters, and when it does, you round the result for display:

js
const sum = 0.1 + 0.2;
console.log(Math.round(sum * 100) / 100); // 0.3

You will also meet a value called NaN, short for "not a number". You get it when a maths operation has no sensible answer, like trying to turn nonsense text into a number:

js
console.log(Number("hello")); // NaN

Seeing NaN in your output usually means a conversion went wrong somewhere, so it is a useful clue rather than something to fear.

Two number values will surprise you eventually, and it is better to meet them on purpose.

The first is floating-point imprecision. Because decimals are stored in binary, some values cannot be held exactly, so 0.1 + 0.2 is not quite 0.3:

js
console.log(0.1 + 0.2 === 0.3); // false

The practical fixes are to round for display, or to work in whole units. Money is the classic case: hold amounts as integer cents (1099 rather than 10.99) and divide only when you show them, which sidesteps the error entirely.

js
const cents = 1099;
console.log(cents / 100); // 10.99, exact for display

The second is NaN, "not a number", the result of an operation with no numeric answer such as Number("hello") or 0 / 0. Its strangest property is that it is not equal to itself:

js
console.log(NaN === NaN); // false

So you cannot test for it with ===. Use Number.isNaN(value), which returns true only for NaN:

js
console.log(Number.isNaN(Number("hello"))); // true

There is also Infinity, what you get from dividing by zero (1 / 0) or overflowing the number range. It behaves like a real value in comparisons but signals that a calculation has run off the edge.

Three values sit at the edges of the number type, and a professional needs all three in reflex memory.

Floating-point imprecision is the famous one. IEEE 754 stores numbers in binary, and values like 0.1 have no exact binary representation, so arithmetic accumulates rounding error:

js
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false

Never compare floats with === when either side came from arithmetic. Two fixes hold up in production: round to a fixed precision for display and comparison, or, for anything where exactness is a requirement like currency, work in the smallest integer unit and only convert at the boundary. Storing money as integer cents (1099) and formatting on output removes the class of bug rather than patching one instance of it.

js
const totalCents = 1099 + 250;
console.log(totalCents); // 1349, exact integer arithmetic

NaN is the number type's error value, produced by 0 / 0, Number("x"), and any operation with an undefined numeric result. It has a unique property mandated by the spec: it is the only value not equal to itself, so NaN === NaN is false. That is why identity checks fail on it and why Number.isNaN(x) exists as the reliable test. Prefer the Number.isNaN form over the older global isNaN, which coerces its argument first and so reports true for things like "hello" that are not actually NaN.

js
console.log(NaN === NaN); // false, NaN never equals itself
console.log(Number.isNaN(0 / 0)); // true, the reliable test

Infinity (and -Infinity) is what you get from 1 / 0 or from exceeding the representable range. It is a real, comparable value (Infinity > 1e308 is true), which makes it a convenient sentinel for "no limit yet" when finding a minimum, but a surprise if it reaches a display or a database. When a result can overflow or divide by zero, decide what a sane fallback is before Infinity leaks downstream.

JunoThe sharp edges of numbers Decimals are stored a bit loosely, so 0.1 + 0.2 comes out as 0.30000000000000004, which is normal, not a bug in your code. When it matters, round the result before you show it. And NaN means "not a number", the answer you get when a conversion fails, so treat it as a clue that some text did not turn into a number the way you expected.
JunoThe sharp edges of numbers Floating-point means 0.1 + 0.2 is not exactly 0.3, so round for display or work in whole units like integer cents for money. NaN is the "no numeric answer" value, and its trap is that NaN === NaN is false, so test with Number.isNaN instead. Infinity shows up from dividing by zero and means a calculation ran off the edge.
JunoThe sharp edges of numbers Never compare arithmetic floats with ===; round to a precision or keep money in integer cents so the error class disappears. NaN is the only value not equal to itself, which is exactly why Number.isNaN exists, and reach for it over the coercing global isNaN. Infinity is a real comparable value from divide-by-zero or overflow, so give it a sane fallback before it reaches a display or a database.

Where numbers go next

Numbers are one of a handful of core data types in JavaScript, sitting alongside text and true/false values. Most real programs mix them, pulling a number out of a string, comparing it, storing the result in a variable, and the operators that do that comparing and combining are the subject of operators next.