3. Week 2 Tuesday: Arithmetic, Integers, and Doubles
≪ 2. Week 1 Thursday: The Box Model and Syntax Errors | Table of ContentsA Quick Note on Integer Arithmetic
Recall that C++ (Mark, Cecil, Steve, whatever you name it)
stores variables in boxes with two labels:
one label for the content and one for the box’s name.
The command int i = 5; creates a box named i
that is designated to contain an integer.
Then consider the following snippet of code:
1int i = 10 / 5;
2int j = 10 / 7;
3int k = 10 / 11;
4cout << i << endl;
5cout << j << endl;
6cout << k << endl;
If we adhere to common core math standards,
we would anticipate the answers \(2\), \(1.4285714\ldots\),
and \(0.90909090\ldots\).
Unfortunately, C++ believes j and k must contain integers,
while the mathematical answers are not.
So C++ does the natural thing:
it lops off everything past the decimal and calls it a day.
Thus the output is 2, 1, and 0, on three separate lines.
This behaviour persists even if we don’t store the values
in an “integer box”.
C++ believes that since 10 and 7 are both integers,
their quotient must also be an integer!
1cout << 10 / 7 << endl; // prints 1
Although frustrating to learn about,
this particular behaviour with integer division
plays a central role in many algorithms,
particularly those that need to chop up lists of data
into a set number of chunks and in certain computations.
An example is division by 10 —
if i is a C++ integer and one writes the code i = i / 10,
what does this do to i?
When might this be useful?
Integer division was born
with a very talented brother named the modulo operator, %.
This is often introduced as the “remainder” operator,
and although this is a perfect characterisation
of what % does for positive numbers,
it is very misleading
and produces confusion for expressions like 21 % -4.
What’s the remainder to 21 / -4?
Instead, think about how division and multiplication
are supposed to cancel each other out.
Mathematically,
if \(a\) and \(b\) are two numbers and \(b\) is nonzero,
we expect
\[(a \div b)\times b = a,\]
no matter what.
However, this is not the case in C++ —
work out what (21 / 4) * 4 is using integer division.
The modulo operator’s purpose is to capture this discrepancy!
Specifically, if a and b are two integers (negative or not),
the quantity a % b is defined as the number satisfying the equation
a = (a / b) * b + a % b.
When a and b are both positive,
this is exactly the same thing as a remainder;
however, when either a or b is negative (or both),
this alternative form continues to be unambiguous.
When Decimals Are Necessary
Remark 1. What the heck is a double?
This is not necessary knowledge for this class, but I think it’s helpful to understand how computers store decimal numbers under the hood.
A single variable in C++ takes up a finite amount of storage on your computer; hence integers cannot store numbers that are too big in any direction (~10 digits). Yet doubles have an impressive range of numbers they can capture. How do they do this?
One should think of a double as a version of scientific notation.
Consider the number
\[6.0047382\times 10 ^{411},\]
which is far too large for an integer to remember
(you would need 411 digits, most of which are zeroes).
Instead, a more efficient storage solution is to remember 6.0047382,
and remember the exponent 411.
This is what your computer does (in binary, more or less),
and it explains why decimals have limited precion!
Numbers with infinite decimal expansions will inevitably
have to be cut off at some point in their scientific notation.
While one can always force a literal number to be a double
by adding a .0 to the end of it,
one has no such luxury with integer variables.
Instead, one needs to “cast” integers into doubles.
To do so, if a is an integer variable,
then static_cast<double>(a) will turn a into a double.
1int a = 19;
2int b = 3;
3
4cout << a / b << endl; // does integer division
5
6cout << static_cast<double>(a / b) << endl; // ???
7cout << static_cast<double>(a) / b << endl;
Note the output of the second line!
You need to cast to a double first, then perform the division.
The second line prints out 6.0 because it does the division first
(which produces the integer 6), then converts that into a double.
The problem of converting a double back into an integer is a similarly slippery problem, one that is perhaps best demonstrated with an example.
Example 2.
Write a C++ program that converts a duration in seconds into minutes, seconds, and milliseconds (you may assume this will be an integer). The program should prompt the user for an the duration in seconds.
SAMPLE RUNS:
Enter a time in seconds: 124.556
124.556s = 2min, 4s, and 556ms.
Let’s first write some pseudocode for this:
- Take a decimal input
timefrom the user. - Define the integer
ms = time * 1000. - Define the integer
seconds = ms / 1000and replacemswithms % 1000. - Define the integer
minutes = seconds / 60and replacesecondswithseconds % 60. - Print out the results to the screen.
We might naïvely cobble together the following program:
1#include <iostream>
2
3using namespace std;
4
5int main() {
6 // 1. take input from user
7 double time;
8 cout << "Enter a time in seconds: ";
9 cin >> time;
10
11 // 2-4. compute ms, seconds, minutes
12 int ms = time * 1000;
13
14 int seconds = ms / 1000;
15 ms = ms % 1000;
16
17 int minutes = seconds / 60;
18 seconds = seconds % 60;
19
20 // 5. print everything out.
21 cout << time << "s = "
22 << minutes << "min, "
23 << seconds << "s, and "
24 << ms << "ms." << endl;
25
26 return 0;
27}
However, consider the following run of the code:
Enter a time in seconds: 16.002
16.002s = 0min, 16s, and 1ms.
Something went wrong, and it’s that C++ thinks 16.002 * 1000
is just barely less than 16002
due to the limited precision of a double!
Thus it rounds it down to 16001 instead.
Common Mistake 3. Converting Doubles to Integers
There are several ways to convert doubles into integers. Consider the following code:
1#include <cmath> // needed for floor, round, ceil
2
3int main() {
4 double d = 20.15;
5 int i1 = d * 100; // truncates d * 100
6 int i2 = floor(d * 100);
7 int i3 = round(d * 100);
8 int i4 = ceil(d * 100);
9
10 return 0;
11}
What values do you think these integer variables hold?
It turns out that i1 and i2 will hold the value 2014
while the other two hold i3 and i4.
What would happen if we defined d = -20.15 instead?
Then i1, i2, and i3 hold the value -2015
while i4 holds -2014.
Be very careful about which method you use
to convert doubles to integers:
they each behave in distinguishable ways,
and they can cause many headaches.
With that out of the way, try to fix the code we wrote above yourself before peeking at the best way to proceed!
Solution
1#include <iostream>
2#include <cmath> // needed for round
3
4using namespace std;
5
6int main() {
7 // 1. take input from user
8 double time;
9 cout << "Enter a time in seconds: ";
10 cin >> time;
11
12 // 2-4. compute ms, seconds, minutes
13 int ms = round(time * 1000);
14
15 int seconds = ms / 1000;
16 ms = ms % 1000;
17
18 int minutes = seconds / 60;
19 seconds = seconds % 60;
20
21 // 5. print everything out.
22 cout << time << "s = "
23 << minutes << "min, "
24 << seconds << "s, and "
25 << ms << "ms." << endl;
26
27 return 0;
28}
In general,
it’s a good idea to use the round function from the cmath library
to convert from doubles to integers
over using the default integer conversion in C++.
Remark 4. The cmath library
In addition to having helpful functions
like floor, ceil, and round,
the cmath library defines a plethora
of helpful mathematical operations
that we may not know how to do otherwise.
The C++ reference
contains a list of every single function accessible
through this library,
some of which (e.g. fmin and fmax)
may be immediately helpful for the first homework.
While the reference can be intimidating, it’s important that you get comfortable reading the documentation! There’s often no good reason to reivent the wheel.