WasiLearn Code
JavaScript · First steps

Numbers, maths and NaN

Three samosas at 35 rupees each. That is the entire program, and JavaScript handles it without ceremony.

let samosas = 3;
let pricePerSamosa = 35;
console.log(samosas * pricePerSamosa);
output
105

The trouble starts when the quantity came from a form, or when the answer has to be split three ways, or when a total that should read 0.3 prints seventeen digits instead. All three have straightforward explanations.

One number type for everything

Many languages separate whole numbers from decimals. JavaScript does not. There is a single number type, and it holds both.

console.log(8 / 2);
console.log(7 / 2);
console.log(typeof 4.5);
console.log(typeof 7);
output
4
3.5
number
number

Dividing 8 by 2 gives 4, not 4.0 — JavaScript prints the shortest form that means the same thing. There is no separate integer type to fall out of, which removes a whole category of problem and creates a different one you will meet at the end of this lesson.

The arithmetic operators

Five you will use constantly, plus one you might not have seen.

let price = 250;
let count = 4;

console.log(price + count);
console.log(price - count);
console.log(price * count);
console.log(price / count);
console.log(price % count);
console.log(2 ** 8);
output
254
246
1000
62.5
2
256

% is the remainder operator: what is left over after dividing. 250 % 4 is 2 because 4 goes into 250 sixty-two times with 2 spare. It is how you test whether a number is even, or work out which row a seat number falls in.

** raises to a power. It has nothing to do with * typed twice.

There are shorthands for updating a variable using its own value:

let runs = 0;
runs = runs + 4;
runs += 6;
runs++;
console.log(runs);
output
11

runs += 6 means runs = runs + 6, and runs++ adds exactly one. Both are so common on scoreboards and counters that reading them fluently is worth the five minutes.

The Math object

Math is a collection of ready-made numerical tools. You never create one — you just use it by name.

console.log(Math.round(4.5));
console.log(Math.round(4.49));
console.log(Math.round(-4.5));
console.log(Math.floor(4.99));
console.log(Math.ceil(4.01));
console.log(Math.max(78, 91, 65));
console.log(Math.min(78, 91, 65));
output
5
4
-4
4
5
91
65

round goes to the nearest whole number, floor always goes down, ceil always goes up. Look at Math.round(-4.5) giving -4, not -5. Exact halves always round upwards on the number line, which for negative numbers means towards zero. It surprises people once and then never again.

floor and ceil are the ones you reach for when the answer must be whole for a real-world reason:

let billTotal = 505;
let friends = 4;

console.log(billTotal / friends);
console.log(Math.ceil(billTotal / friends));
output
126.25
127

Nobody hands over a quarter of a rupee, so rounding up is the honest answer.

Random numbers, and why this page cannot print one

Math.random() returns a decimal that is at least 0 and always less than 1. To turn that into a dice roll you multiply by 6 — giving something from 0 up to just under 6 — chop the decimal off with Math.floor, and add 1.

The general recipe for a whole number from min to max inclusive is Math.floor(Math.random() * (max - min + 1)) + min.

Every code block on this site prints exactly what it says it prints, and that is impossible for a random number. So instead of printing the roll, here is proof it lands where it should:

let roll = Math.floor(Math.random() * 6) + 1;

console.log(Number.isInteger(roll));
console.log(roll > 0);
console.log(roll < 7);
output
true
true
true

Press Run as many times as you like. The roll changes each time and all three answers stay true. Drop the Math.floor and the first line would turn false, which is exactly the bug to look for when random numbers come out with decimals attached.

Turning text into numbers

Anything typed into a page arrives as a string, as the data types lesson showed. Two tools convert it, and they disagree in a way that matters.

console.log(Number("42"));
console.log(parseInt("42"));
console.log(parseInt("42 rupees"));
console.log(Number("42 rupees"));
console.log(parseInt("3.9"));
console.log(parseFloat("3.9 km"));
output
42
42
42
NaN
3
3.9

Number is strict: the whole string must be a valid number or you get nothing useful. parseInt is relaxed: it reads from the left, stops at the first character it cannot use, and keeps what it got. That is why parseInt("3.9") gives 3 — the decimal point ends the whole number it was reading.

One more disagreement worth knowing:

console.log(Number(""));
console.log(parseInt(""));
output
0
NaN

An empty box becomes zero under Number. If your form treats a blank field as a genuine 0, that is a bug wearing a disguise.

NaN: the answer that is not a number

When a calculation cannot produce a sensible number, JavaScript does not stop. It produces NaN — Not a Number — and carries on.

let typedAmount = "twelve hundred";
let orderTotal = Number(typedAmount) * 2;

console.log(Number(typedAmount));
console.log(orderTotal);
console.log(typeof orderTotal);
console.log(NaN + 1);
output
NaN
NaN
number
NaN

Two things to take from that. First, typeof NaN is number, which reads like a joke but is technically correct: it is a number-typed value meaning "this calculation failed". Second, NaN is contagious. Every sum, product or average it touches becomes NaN, so by the time you see one on screen the real mistake may be many lines earlier.

Why === NaN never works

You have a total that might be broken, so you check for it the way you would check for any other value. === means "is exactly this", and it is covered properly in the equality lesson.

let brokenTotal = Number("free") * 100;

console.log(brokenTotal === NaN);
console.log(NaN === NaN);
console.log(Number.isNaN(brokenTotal));
output
false
false
true

brokenTotal is NaN. The test says false. Nothing is broken and nothing is lying: NaN is the only value in JavaScript that is not equal to itself.

The reasoning behind that rule is that NaN is not one value, it is a label for "the answer does not exist". The square root of a negative and the result of Number("free") are both failures, but they are not the same failure, so declaring them equal would be wrong. Rather than invent an ordering for non-answers, the standard says every comparison involving NaN is false.

So you never test with ===. You use Number.isNaN, which asks the question directly. Be careful to pick the right one — there is an older global isNaN that converts its argument first and therefore lies:

console.log(isNaN("hello"));
console.log(Number.isNaN("hello"));
output
true
false

"hello" is a string. It is not NaN. Number.isNaN tells the truth; the bare isNaN reports on what the value would become if converted. Prefer Number.isNaN every time.

One curiosity falls out of the rule: since NaN is the only value unequal to itself, total !== total is a valid NaN test. You will see it in older code. Number.isNaN says the same thing in a way people can read.

0.1 + 0.2 is not 0.3

console.log(0.1 + 0.2);
console.log(0.1 + 0.2 === 0.3);
console.log(0.5 + 0.25);
output
0.30000000000000004
false
0.75

This is not a JavaScript flaw; almost every language does it. Numbers are stored in binary, and 0.1 in binary is a repeating fraction the same way a third is in decimal. It gets stored to the nearest available value, and the tiny error surfaces when you add two of them. 0.5 and 0.25 are exact halves of powers of two, so they behave perfectly.

Count in paisa, format at the last moment

The fix for money is to stop storing fractions. Work in the smallest whole unit — paisa rather than rupees — so every value is an exact whole number, and divide only when you are about to show it.

let itemPaisa = 10;
let otherPaisa = 20;
let totalPaisa = itemPaisa + otherPaisa;

console.log(totalPaisa);
console.log(totalPaisa / 100);
output
30
0.3

When you do need to display a messy decimal, toFixed rounds it to a set number of places — and hands you back a string, which the strings lesson explains the consequences of.

let messy = 0.1 + 0.2;

console.log(messy.toFixed(2));
console.log(typeof messy.toFixed(2));
output
0.30
string

Use it for the last step before printing, never for a value you intend to keep adding to.

Prefer learning with a teacher?

WasiLearn Academy runs small-group classes covering this material, online and in Karachi.

See the classes