JavaScript lesson 3 of 9
JavaScript Arithmetic and Numbers
How JavaScript does math - why there is only one number type, what the remainder operator really answers, powers, the Math object, and why 0.1 + 0.2 is not 0.3.
Published · Every example on this page was run before it was published.
Printing text is how a program talks. Arithmetic is how it works something out before it speaks. A checkout page totals a bill, a timer turns a pile of seconds into a clock reading, a scoreboard adds one more point — all of that is numbers being combined, with a handful of operators you can learn in a single sitting.
JavaScript's arithmetic looks like the sums you did on paper, and most of the time it behaves that way. Two plus three really is five. But a few details are genuinely different, for reasons worth understanding rather than just memorising. JavaScript keeps only one kind of number, which changes what "division" even means. There is no built-in operator for "how many whole groups fit," so you reach for a function instead. And numbers with a decimal point carry a small permanent inaccuracy that will turn up in your output whether you expect it or not.
This lesson takes the operators in the order you meet them, then spends real time on the things that
catch people out: what a single number type means for division, what % is actually for, and why
0.1 + 0.2 refuses to equal 0.3. Nothing here goes beyond the variables and types from the lesson
before it.
The Operators You Write Every Day
Addition, subtraction, multiplication and division are written +, -, * and /. JavaScript works
the answer out the instant it reads the line, and variables serve just as well as plain numbers:
const apples = 12;
const oranges = 5;
console.log(apples + oranges);
console.log(apples - oranges);
console.log(apples * oranges);
console.log(apples / oranges);17
7
60
2.4Multiplication is the one that looks unfamiliar if you are new to code. On paper you would write a
small x, or a dot, or nothing at all beside a bracket. JavaScript accepts none of those: the letter x
is a perfectly good variable name, so it cannot double as an operator. Multiplication is always a
star, and there is no implied multiplication — 2(width + height) is not valid JavaScript; you must
write 2 * (width + height).
Division and JavaScript's One Number Type
Here is the detail that shapes everything else in this lesson: JavaScript has exactly one numeric
type, simply called number. There is no separate type for whole numbers and another for decimals —
7 and 7.5 are both just numbers, and typeof reports "number" for either one.
That single type changes what division looks like. / always performs true division, decimal point
and all, but because there is no separate "whole number" type to preserve, a division that comes out
exactly even prints as a plain integer, with nothing to show that a division happened at all:
console.log(10 / 2);
console.log(9 / 3);
console.log(100 / 4);
console.log(typeof (10 / 2));5
3
25
numberTen divided by two is five, and it prints as 5, not 5.0. That is not a special case — JavaScript
simply has no separate way to write "the integer five" and "the number five that happens to have no
remainder." Keep this in mind while reading other people's code: a bare 5 in a console.log tells
you nothing about whether it came from 5, 10 / 2, or 2.5 * 2.
Whole Groups and What's Left Over
Plain division is not the only useful answer to "how does this number split up." Picture a hundred eggs going into cartons that hold a dozen. Two answers are true at once here, and neither of them is 8.33. The first is how many cartons you can close and stack: eight. The second is how many eggs are still sitting on the counter with nowhere to go: four.
Unlike some languages, JavaScript has no dedicated operator for "how many whole groups fit." You build
it from division and Math.floor(), a function that rounds down to the nearest whole number. What it
does have a dedicated operator for is the leftover: %, the remainder operator.
console.log(Math.floor(100 / 12));
console.log(100 % 12);8
4Math.floor(100 / 12) divides first and then rounds the 8.333... down to 8. % reports what is
left over after removing as many complete groups of 12 as possible — a job it does directly, without
you having to divide at all.
The pair earns its keep as soon as the numbers are too big to hold in your head. A school trip takes a hundred and thirty-seven students, and each coach seats forty-five:
const students = 137;
const seatsPerCoach = 45;
const fullCoaches = Math.floor(students / seatsPerCoach);
const stillWaiting = students % seatsPerCoach;
console.log("Students:", students);
console.log("Full coaches:", fullCoaches);
console.log("Still waiting:", stillWaiting);
console.log("Back to the total:", fullCoaches * seatsPerCoach + stillWaiting);Students: 137
Full coaches: 3
Still waiting: 2
Back to the total: 137That last line is the reason to trust the pair. Multiply the whole groups back up, add the remainder, and you land exactly on the number you started with. That relationship always holds, so it is the quickest way to check you have used the two correctly.
% also gives you the standard way to ask whether a number divides evenly, which in practice usually
means asking whether it is even:
console.log(10 % 2);
console.log(11 % 2);
console.log(2506 % 10);0
1
6An even number leaves nothing when divided by two, so number % 2 is 0 for evens and 1 for odds.
The third line applies the same idea to digits: % 10 peels off the final digit of a number.
Powers with **
Two stars mean "raised to the power of." 2 ** 3 is three twos multiplied together:
console.log(2 ** 3);
console.log(5 ** 2);
console.log(10 ** 6);
console.log(9 ** 0.5);
console.log(2 ** 0.5);8
25
1000000
3
1.414213562373095110 ** 6 is a million, tidier than counting zeros by hand. A fractional power is a root — raising to
the power of 0.5 is the square root, so 9 ** 0.5 is 3. The square root of two on the last line is
an endless decimal, and 1.4142135623730951 is as close as JavaScript can store — the same limit you
will meet again in the next section. An older function, Math.pow(2, 3), does the same job as 2 ** 3
and still turns up in code written before the ** operator existed.
One sharp edge is worth knowing about before it surprises you: JavaScript refuses to let a unary minus
sit directly in front of ** without parentheses, because -3 ** 2 is genuinely ambiguous — it could
mean "negative three, squared" or "the negative of three squared," and those give different answers.
Rather than silently picking one, JavaScript treats -3 ** 2 as a syntax error and makes you say which
you meant:
console.log(-(3 ** 2));
console.log((-3) ** 2);-9
9The first parenthesises the power first and negates the result; the second negates three before squaring it. Once you have seen both spelled out, you will not want to write the ambiguous version even where JavaScript allowed it.
The Math Object
Beyond **, most numeric tools live on a built-in object called Math. You reach them with a dot, the
same way you reach a string method:
console.log(Math.round(4.9));
console.log(Math.round(4.1));
console.log(Math.floor(4.9));
console.log(Math.ceil(4.1));
console.log(Math.trunc(4.9));
console.log(Math.trunc(-4.9));
console.log(Math.abs(-7));
console.log(Math.max(3, 9, 1));
console.log(Math.min(3, 9, 1));5
4
4
5
4
-4
7
9
1Math.round() rounds to the nearest whole number. Math.floor() always rounds down and Math.ceil()
always rounds up, regardless of how close the decimal part is. Math.trunc() does something subtly
different from both: it chops off the decimal part entirely and moves toward zero, which is why
Math.trunc(-4.9) gives -4 rather than -5 — Math.floor(-4.9), by contrast, would give -5,
because "down" for a negative number means further from zero. Math.abs() strips a sign, and
Math.max()/Math.min() accept as many arguments as you like and return the largest or smallest.
Why 0.1 + 0.2 Is Not 0.3
This one deserves to be shown before it is explained:
console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
console.log(0.1 + 0.2 - 0.3);0.30000000000000004
false
5.551115123125783e-17That is not a typo, a bug, or something wrong with your browser. It is how floating-point numbers work, and Python, Java, C, Ruby and essentially every other mainstream language give the same strange answer, because they nearly all store decimal numbers the same underlying way.
The reason, in plain terms: a computer stores numbers in binary, as sums of halves, quarters, eighths
and so on, and some decimal fractions have no exact form in that system. One tenth is one of them, in
exactly the way one third has no exact form in ordinary decimal notation — writing 0.3333 and adding
threes forever never quite reaches a third. So JavaScript stores the closest value to 0.1 that it
can, which is a hair off, and adding two hairs-off numbers can produce an error big enough to see. That
last line, 5.551115123125783e-17, is shorthand for a number with sixteen zeros after the decimal
point before the digits begin: a vanishingly small gap, but not zero, and === does not forgive gaps.
Since you cannot tell by looking which sums will drift, the practical rule has to apply everywhere:
never compare two calculated numbers with === when either one might carry a decimal point. Ask
instead whether they are close enough:
const total = 0.1 + 0.2;
console.log(Math.abs(total - 0.3) < 0.000001);
console.log(Number(total.toFixed(2)) === 0.3);true
trueThe first line subtracts one number from the other, takes the size of the difference with Math.abs()
so the sign cannot matter, and checks that it is tiny. The second uses .toFixed(2), a method that
formats a number to a fixed number of decimal places as a string — "0.30" here — which Number()
then converts back for the comparison.
A second strategy is stronger, and it is the one to reach for with money: work in whole units. Store paise rather than rupees, or cents rather than dollars, keep the arithmetic in whole numbers where nothing can drift, and split the result only for display:
const pricePaise = 1075;
const quantity = 3;
const totalPaise = pricePaise * quantity;
console.log("Total in paise:", totalPaise);
console.log("Rupees:", Math.floor(totalPaise / 100));
console.log("Paise:", totalPaise % 100);Total in paise: 3225
Rupees: 32
Paise: 25Math.floor() and % reappear here to do the same job as before: how many whole rupees, and what is
left over. This arithmetic is exact because whole numbers below about 9 quadrillion are stored exactly
in JavaScript's number type — the drift only shows up once a decimal point is involved.
Negative Numbers and the Remainder Operator
% is often described as "what's left over after dividing," and for positive numbers that
description works fine. For negative numbers, it is worth seeing exactly what JavaScript does, because
the rule is not always the one you would guess:
console.log(7 % 2);
console.log(-7 % 2);
console.log(7 % -2);1
-1
1JavaScript's % always gives a result with the same sign as the number on the left, regardless of
the sign on the right. -7 % 2 is -1, not 1, because the dividend, -7, is negative. This matters
if you ever use % to keep a value inside a range — a common trick for wrapping an index around the
end of a list — because a negative input can hand you back a negative remainder instead of the
positive one you were expecting, and you may need ((n % size) + size) % size to force it positive.
Updating a Number You Already Have
Adding to a running total is common enough that JavaScript has shorthand for it. score += 5 means
exactly score = score + 5: work the sum out from the current value, then rebind the name to the
result. The same shorthand exists for the other operators.
let score = 10;
console.log(score);
score += 5;
console.log(score);
score -= 3;
console.log(score);
score *= 2;
console.log(score);
score /= 4;
console.log(score);10
15
12
24
6Follow the value down: 10, add 5 to get 15, subtract 3 to get 12, double it to 24, then divide by 4 to
get 6. For the specific case of adding or subtracting exactly 1, JavaScript has an even shorter form:
count++ and count--.
let count = 5;
count++;
console.log(count);
count--;
console.log(count);6
5A Worked Example
Here is a small complete program that turns a count of watched videos into hours, minutes and seconds,
using multiplication for the total, Math.floor() and % to break it down, and .toFixed() to keep a
percentage readable.
const secondsPerVideo = 245;
const videosWatched = 18;
const dailyLimitHours = 3;
const totalSeconds = secondsPerVideo * videosWatched;
const hours = Math.floor(totalSeconds / 3600);
const remainingAfterHours = totalSeconds % 3600;
const minutes = Math.floor(remainingAfterHours / 60);
const seconds = remainingAfterHours % 60;
const percentOfLimit = (totalSeconds / (dailyLimitHours * 3600)) * 100;
console.log("==============================");
console.log(" WATCH TIME SUMMARY");
console.log("==============================");
console.log("Videos watched ...", videosWatched);
console.log("Seconds each .....", secondsPerVideo);
console.log("Total seconds ....", totalSeconds);
console.log("------------------------------");
console.log("Hours ............", hours);
console.log("Minutes ..........", minutes);
console.log("Seconds ..........", seconds);
console.log(`Percent of limit .. ${percentOfLimit.toFixed(1)}%`);
console.log("==============================");==============================
WATCH TIME SUMMARY
==============================
Videos watched ... 18
Seconds each ..... 245
Total seconds .... 4410
------------------------------
Hours ............ 1
Minutes .......... 13
Seconds .......... 30
Percent of limit .. 40.8%
==============================The first three lines name the facts the program is built on, so changing the video count means
editing one line rather than hunting through the calculations. secondsPerVideo * videosWatched gives
4410 total seconds. Math.floor(4410 / 3600) is 1, the one full hour those seconds contain, and
4410 % 3600 is 810, the seconds left over once that hour is removed. The same pattern applies one
level down: Math.floor(810 / 60) gives 13 whole minutes, and 810 % 60 gives 30 leftover
seconds — the same divide-and-remainder idea from earlier in the lesson, just applied twice in a row to
peel off two units instead of one.
The last calculation divides the total by the size of a three-hour limit in seconds and multiplies by
100 to get a percentage. That division does not come out to a tidy number — 40.833333... — which is
exactly why .toFixed(1) is there: it rounds for display without touching the underlying value, the
same distinction you saw with 0.1 + 0.2 earlier in this lesson.
Common Mistakes
Assuming every operator treats a numeric string the same way
console.log("10" - 3);
console.log("10" * 2);
console.log("10" + 3);7
20
103- and * have only one sensible meaning for JavaScript to fall back on, so they convert a string
operand to a number before doing the math. + is different: it is also the string concatenation
operator, and when either side is already a string, JavaScript concatenates instead of adding. The
result is that "10" - 3 and "10" + 3 look like they should behave the same way and do not. When you
mean arithmetic, convert with Number() first rather than relying on which operator you happened to
use.
Trusting === to catch a NaN
const price = Number("12.5kg");
console.log(price);
console.log(price + 10);
console.log(price === price);NaN
NaN
falseNumber() cannot make sense of a string with stray letters in it, so it gives up entirely and returns
NaN. Any arithmetic that touches a NaN produces another NaN, which is why it can travel silently
through several calculations before you notice. The last line is the real trap: NaN is defined to be
unequal to everything, including itself, so price === price is false. Checking price === NaN
would be just as useless. The correct test is Number.isNaN(price), which returns true here and is
the only reliable way to ask "did this turn into NaN?"
Expecting division by zero to throw an error
console.log(5 / 0);
console.log(-5 / 0);
console.log(0 / 0);Infinity
-Infinity
NaNDividing by zero does not stop a JavaScript program the way it does in many other languages — there is
no error at all. 5 / 0 produces the special value Infinity, -5 / 0 produces -Infinity, and
0 / 0, which has no sensible answer in either direction, produces NaN. Because nothing crashes,
a stray division by zero can quietly turn an entire calculation into Infinity or NaN several steps
before you notice something is wrong. Guard against a zero divisor explicitly when it is a realistic
possibility, such as dividing by the length of a list that might be empty.
Next Steps
Of the linked practice problems, two-number-sum puts plain addition to work while you keep track of
which values are genuinely numbers rather than numeric-looking strings. binary-search is where
Math.floor() earns its keep: finding the middle of a range is Math.floor((low + high) / 2), and the
Math.floor() is not optional — a list position must be a whole number, and a fractional index is a
bug, not a rounded guess.
Before either, spend a few minutes in the JavaScript playground. Compare
10 / 2 and Math.floor(10 / 2) side by side until the difference feels obvious rather than
surprising. Try % with a few negative numbers and confirm for yourself which sign wins. Then type
0.1 + 0.2 once more, so that when it appears in your own output six months from now you recognise it
immediately instead of losing an afternoon to it.
When you want a harder one
Interview-style problems graded against hidden tests — a big step up from the exercises. Come back to these when the ideas in this lesson feel comfortable rather than new.