Skip to content

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:

JavaScript
const apples = 12;
const oranges = 5;

console.log(apples + oranges);
console.log(apples - oranges);
console.log(apples * oranges);
console.log(apples / oranges);
Output
17
7
60
2.4

Multiplication 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:

JavaScript
console.log(10 / 2);
console.log(9 / 3);
console.log(100 / 4);
console.log(typeof (10 / 2));
Output
5
3
25
number

Ten 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.

JavaScript
console.log(Math.floor(100 / 12));
console.log(100 % 12);
Output
8
4

Math.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:

JavaScript
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);
Output
Students: 137
Full coaches: 3
Still waiting: 2
Back to the total: 137

That 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:

JavaScript
console.log(10 % 2);
console.log(11 % 2);
console.log(2506 % 10);
Output
0
1
6

An 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:

JavaScript
console.log(2 ** 3);
console.log(5 ** 2);
console.log(10 ** 6);
console.log(9 ** 0.5);
console.log(2 ** 0.5);
Output
8
25
1000000
3
1.4142135623730951

10 ** 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:

JavaScript
console.log(-(3 ** 2));
console.log((-3) ** 2);
Output
-9
9

The 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:

JavaScript
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));
Output
5
4
4
5
4
-4
7
9
1

Math.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 -5Math.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:

JavaScript
console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
console.log(0.1 + 0.2 - 0.3);
Output
0.30000000000000004
false
5.551115123125783e-17

That 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:

JavaScript
const total = 0.1 + 0.2;

console.log(Math.abs(total - 0.3) < 0.000001);
console.log(Number(total.toFixed(2)) === 0.3);
Output
true
true

The 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:

JavaScript
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);
Output
Total in paise: 3225
Rupees: 32
Paise: 25

Math.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:

JavaScript
console.log(7 % 2);
console.log(-7 % 2);
console.log(7 % -2);
Output
1
-1
1

JavaScript'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.

JavaScript
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);
Output
10
15
12
24
6

Follow 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--.

JavaScript
let count = 5;
count++;
console.log(count);
count--;
console.log(count);
Output
6
5

A 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.

JavaScript
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("==============================");
Output
==============================
     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

JavaScript
console.log("10" - 3);
console.log("10" * 2);
console.log("10" + 3);
Output
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

JavaScript
const price = Number("12.5kg");
console.log(price);
console.log(price + 10);
console.log(price === price);
Output
NaN
NaN
false

Number() 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

JavaScript
console.log(5 / 0);
console.log(-5 / 0);
console.log(0 / 0);
Output
Infinity
-Infinity
NaN

Dividing 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.