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
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:1Three 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:
| Input | Output |
|---|---|
| 59 | 0:0:59 |
| 3661 | 1:1:1 |
| 7325 | 2:2:5 |
| 86399 | 23: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.