Contents

Chapter 6

Arithmetic, and the Number Type's Sharp Edges

One numeric type for everything, and what that costs you.

Chapter 3 gave you number and mentioned, in passing, that TypeScript uses one type where plenty of languages use two or three. That was a promise of a bill coming due. This is the chapter where it arrives.

Most of arithmetic is exactly what you’d expect, so let’s get through that part quickly and spend the time where it’s actually interesting.

The operators

console.log(10 - 3);
console.log(4 * 2.5);
console.log(7 / 2);
7
10
3.5

Nothing surprising, and one thing worth pointing at: 7 / 2 is 3.5.

If you’ve read about programming before, you may have been warned that dividing two whole numbers throws the fraction away and gives you 3. That is true in a lot of languages, C++ among them, and it’s a classic source of quietly wrong answers. It is not true here. TypeScript has one numeric type, so there’s no such thing as “an integer division” for it to fall into. / always gives you the real answer.

That’s the upside of one number type, and it’s genuine. Hold onto it, because the rest of this chapter is the other side.

The remainder

% gives you what’s left over after a division:

console.log(7 % 2);
console.log(5.5 % 2);
1
1.5

Seven divided by two is three with one left over, so 7 % 2 is 1. It works on fractions too, since again, there’s only one kind of number here.

% is more useful than it looks. “Is this number even” is n % 2 === 0, and “how many seconds are left after taking out whole minutes” is n % 60, which is exactly what this chapter’s project needs.

Order of operations

*, / and % happen before + and -, the same rule as school maths. Parentheses override it:

console.log(2 + 3 * 4);
console.log((2 + 3) * 4);
14
20

When in doubt, use parentheses. They cost nothing and they mean the next person to read the line doesn’t have to remember a table.

Changing a value with what’s already in it

let total = 5;
total += 3;
total *= 2;
console.log(total);
16

total += 3 means “add 3 to whatever total already holds.” There’s a version of this for each operator, -=, *=, /=, and they all read the same way. There’s also total++, which adds exactly 1 and is what you’ll see most often once loops arrive in chapter 8.

The bill, part one: decimals

Here’s the cost chapter 3 promised:

console.log(0.1 + 0.2);
0.30000000000000004

That is not a typo, and your computer isn’t broken. Numbers are stored in binary, and one tenth in binary is a repeating fraction, the same way one third in decimal is 0.333... forever. It has to be cut off somewhere, so 0.1 isn’t exactly one tenth, it’s extremely close. Add two extremely close numbers and the tiny errors show up.

Which means this, which looks like it must be true, isn’t:

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

For displaying money, .toFixed() rounds to a fixed number of decimal places:

console.log((0.1 + 0.2).toFixed(2));
console.log((99).toFixed(2));
0.30
99.00

That solves the chapter 3 project’s slightly-bare 99 too. One catch worth knowing: .toFixed() gives you back a string, not a number, so it’s the last thing you do before printing, not something to keep doing arithmetic with.

The bill, part two: very large whole numbers

The other cost is at the far end of the scale:

console.log(Number.MAX_SAFE_INTEGER);
console.log(Number.MAX_SAFE_INTEGER + 1);
console.log(Number.MAX_SAFE_INTEGER + 2);
9007199254740991
9007199254740992
9007199254740992

Look at the last two lines. Two different sums produced the same answer. Past about nine quadrillion, there are more whole numbers than number has room to tell apart, so some of them share.

You are unlikely to hit this. It’s worth knowing anyway, because it’s the clearest possible statement of what “one type for everything” means: number is a compromise that covers the enormous middle of what anyone needs, and gives up a little at both ends.

Exercise 1 · Watch the edge

Print Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2 and confirm you get true. Two sums that are obviously different, comparing as equal.

It’s the same underlying reason as 0.1 + 0.2: a fixed amount of room to store a number in, and not every number fitting. Seeing both ends of it once makes the middle make sense.

Dividing by zero doesn’t crash

In some languages this is a disaster. Here it’s just an answer:

console.log(1 / 0);
console.log(-1 / 0);
console.log(0 / 0);
Infinity
-Infinity
NaN

Infinity is a real value you can hold in a variable and print. And 0 / 0 gives you NaN, which you already met in chapter 5 when Number("banana") produced it.

Nothing crashes, nothing is undefined, the program keeps running. That’s genuinely kinder than the alternative, but it does mean a division by zero slips through silently and turns up later as an Infinity in your output. Same lesson as NaN: recognise it on sight.

Getting a whole number on purpose

Since / never truncates, “how many whole hours is this” needs asking for:

console.log(7 / 2);
console.log(Math.floor(7 / 2));
3.5
3

Math.floor() rounds down to a whole number. There’s Math.ceil() to round up and Math.round() for nearest. Math.floor() is the one that does the job the project needs.

Check yourself

1. What does 7 / 2 give you in TypeScript?

Not quite. That is what a language with a separate integer type does. TypeScript has one number type, so there is no integer division to fall into.

Yes. Division always gives the real answer. Getting a whole number is something you ask for with Math.floor().

Not quite. Nothing to error about. 3.5 is a perfectly ordinary number here.

2. const price = 10; console.log("Total: " + price + 5); What prints?

Not quite. That is what a template literal would give you. Plain + works left to right, and the left-hand side has already become text by the time it reaches 5.

Yes. "Total: " + price makes the string "Total: 10", and then + 5 appends rather than adds. TypeScript allows it because each step is individually legal.

Not quite. + is deliberately allowed to join a string and a number. It is the one arithmetic operator TypeScript cannot be strict about.

3. Why is 0.1 + 0.2 === 0.3 false?

Not quite. === compares numbers fine. The problem is that the two sides genuinely hold different values.

Yes. One tenth in binary repeats forever and has to be cut off, so the stored value is close but not exact. The tiny errors survive the addition.

Not quite. Nothing is being rounded. The sum prints as 0.30000000000000004, which is the actual stored value.

Project

Seconds into hours, minutes and seconds

Roughly 30 minutes

Take a number of seconds and print it as hours, minutes and seconds. Start with the number hardcoded in a const.

From 3725, produce:

3725 seconds is 1h 2m 5s

The shape of it:

  1. Hours are how many whole 3600s fit in the total. Division alone gives you 1.034..., so Math.floor() is doing real work here.
  2. Minutes are the whole minutes in what’s left over after the hours come out. That leftover is exactly what % gives you, so this step uses both operators: Math.floor((total % 3600) / 60).
  3. Seconds are what’s left after whole minutes, which is just total % 60.
  4. Join it with a template literal.

Rules: one const for the input at the top, and changing only that number should give a correct answer for any value you put in it. No copy-pasted arithmetic with different numbers written in by hand.

Test it properly. The interesting inputs are the boundaries, not the middle:

InputShould print
590h 0m 59s
600h 1m 0s
36001h 0m 0s
8639923h 59m 59s
00h 0m 0s

If any of those come out wrong, the usual culprit is a missing Math.floor() giving you a decimal, or taking % 60 of the wrong number.

Stretch: print 1h 02m 05s instead, with the minutes and seconds padded to two digits, the way a clock shows them. You’ll need to build a string and check its length, which is chapter 4’s territory. Doing it without if is awkward on purpose, and chapter 7 makes it easy.