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