Contents

Chapter 6

Arithmetic, and Where It Betrays You

Integer division, remainder, precedence, and numbers that aren't quite right.

Arithmetic looks like the safe part. You already know what + does, and school covered the rest twenty years ago.

This is the chapter where C++ gives you a wrong answer without a single warning.

The five operators

std::cout << 7 + 2 << '\n';
std::cout << 7 - 2 << '\n';
std::cout << 7 * 2 << '\n';
std::cout << 7 / 2 << '\n';
std::cout << 7 % 2 << '\n';
9
5
14
3
1

Four of those are unremarkable. Look at the fourth one again.

Integer division throws the remainder away

7 / 2 is 3. Not 3.5, and not 4. It isn’t rounding, it’s chopping. The fractional part is discarded and never mentioned again.

The reason is consistency. Both 7 and 2 are ints, whole numbers, so C++ gives you back an int. It won’t invent a fraction you didn’t ask for, and it won’t quietly change the type of your answer.

That means 9 / 10 is 0, which is the version that ruins someone’s afternoon.

The remainder is the other half of the answer

% hands you exactly what / threw away:

std::cout << 7 / 2 << " remainder " << 7 % 2 << '\n';
3 remainder 1

Seven is two twos with one left over. It’s called the modulo operator, and it only works on whole numbers, and asking for the remainder of a decimal isn’t a question that makes sense.

Together / and % split any quantity into units. How many whole minutes are in 200 seconds, and how many seconds are left? 200 / 60 and 200 % 60. That’s the project at the end of this chapter.

Mixing whole numbers and decimals

One double anywhere in the sum is enough to change the outcome:

std::cout << 7 / 2.0 << '\n';
std::cout << 7.0 / 2 << '\n';
3.5
3.5

If either side has a fractional part, C++ treats the whole expression as decimal work and gives you a double back. This also works with variables, which is the common case:

double bill = 47.5;
int people = 4;
std::cout << bill / people << '\n';
11.875

bill is a double, so the division is done in decimal even though people isn’t. The rule to carry: whole numbers only make whole numbers. Everything else is fine.

Which operation happens first

*, / and % are done before + and -, exactly as in school:

std::cout << 1 + 2 * 3 << '\n';
std::cout << (1 + 2) * 3 << '\n';
7
9

C++ has a precedence table with about seventeen levels in it. You could learn it. Nobody does. Use brackets when there’s any doubt. They cost nothing, they never go wrong, and the person reading your code in six months is you.

Doing something to a variable

int score = 10;
score += 5;   // 15
score *= 2;   // 30
score /= 4;   // 7

score += 5 means score = score + 5, and the same pattern works for -=, *= and /=. It saves typing the variable name twice, which also means one fewer place to typo it.

Note that last line: 30 divided by 4 gives 7, not 7.5. score is an int, so integer division applies here too. The rule doesn’t go away because the syntax got shorter.

Doubles don’t hold exactly what you think

Chapter 3 promised that double would eventually behave strangely. Here it is:

double a = 0.1 + 0.2;
std::cout << a << '\n';
0.3

That looks fine. But cout is being polite. It rounds to six significant digits by default. Ask it to show more:

#include <iomanip>

std::cout << std::setprecision(17) << a << '\n';
0.30000000000000004

That is the actual value. 0.1 + 0.2 is not 0.3, and never was.

The cause isn’t C++ being careless. Computers store numbers in binary, and 0.1 can’t be written exactly in binary any more than a third can be written exactly in decimal, since 0.3333… never finishes. So the machine keeps the closest value it can, and the tiny gaps add up.

Dividing by zero

With doubles, C++ has answers ready:

std::cout << 7.0 / 0.0 << '\n';
std::cout << -7.0 / 0.0 << '\n';
std::cout << 0.0 / 0.0 << '\n';
inf
-inf
nan

inf is infinity, and nan means “not a number”, the result of a question with no sensible answer. They spread, too: anything you add to nan is nan.

With whole numbers, there is no answer at all.

Exercise 1 · Predict, then run

Write down your answers before compiling anything:

std::cout << 9 / 10 << '\n';
std::cout << 9 % 10 << '\n';
std::cout << 9 / 10.0 << '\n';
std::cout << 10 / 3 * 3 << '\n';
std::cout << 1 + 2 * 3 - 4 / 2 << '\n';

The fourth one is the interesting one. 10 / 3 * 3 is not 10, and understanding why is the whole chapter in a single line.

Check yourself

1. What does 7 / 2 print when both are ints?

Not quite. That is what you get if either side is a double. Two ints give an int back.

Not quite. There is no rounding. The fractional part is discarded, not weighed.

Yes. Chopped, not rounded, which is why 9 / 10 gives 0 rather than 1.

2. double average = 7 / 2; what does average hold?

Yes. The division finishes before the assignment happens. By the time the double is involved, the answer is already 3.

Not quite. The type of the variable cannot reach backwards and change how the division was done.

Not quite. Nothing is wrong as far as C++ is concerned. That is what makes it dangerous: no error, no warning, wrong answer.

3. An int divided by zero at runtime: what happens?

Not quite. Usually on x86, yes. On Apple Silicon the same program printed 0 and kept going.

Yes. The standard declines to say, so you get whatever the hardware does: a crash on some chips, a silent 0 on others.

Not quite. Doubles have inf and nan available. Whole numbers have no way to represent either.

Project

Seconds into hours, minutes and seconds

Roughly 40 minutes

Ask for a number of seconds and print it as a proper duration.

How many seconds? 3661
1:1:1

Three lines of arithmetic do the whole job, and each one is / or %:

  • hours: how many whole 3600s fit in the total
  • minutes: take what’s left after the hours, and see how many whole 60s fit
  • seconds: what’s left after that

Getting the middle one right is the puzzle. total / 60 counts all the minutes, including the ones already counted as hours, so you need the remainder first and then divide it.

Check it against these:

InputOutput
590:0:59
36611:1:1
73252:2:5
8639923:59:59

Stretch one, make it look like a clock. 1:1:1 should really be 1:01:01. Padding numbers with zeros needs two tools from <iomanip>: std::setfill('0') and std::setw(2) before each number you want padded.

Stretch two, a tip splitter. Read a bill, a tip percentage and a number of people, then print what each person pays. Keep the money in doubles and the people in an int, and watch where the division does what you expect and where it doesn’t.