3. Week 2 Tuesday: Working with Numbers
≪ 2. Week 1 Thursday: Integer Operations | Table of Contents | 4. Week 2 Thursday: Increments, Decrements, and Strings ≫I had planned to discuss integer division and the modulo operator last Thursday, but I did not get to discuss them in full detail. So we’ll reiterate some things that I didn’t get to describe fully, and we’ll also describe some messy details involving numbers in C++.
Integer Division and the Modulo Operator
Whenever one performs an arithmetic operation in C++ with two integers, one will always end up with another integer. This is not a problem most of the time: the sum, difference, and product of two integers is always an integer anyways. However, the quotient of two integers is not always an integer! Consider the following code snippet:
The output of this code consists of three numbers: 6
, 6
, and -6
, each on its own line. C++’s method of converting the mathematical answers — \(6\), \(6.3333\ldots\), and \(-6.6666\ldots\) — into integers is simply to cut off everything from the decimal point onwards.
Common Mistake 1. C++ Does Not Round
It should be pointed out that the conversion is not the same as rounding. \(-6.666\ldots\) in the previous example got converted to -6
, yet conventional rounding would produce -7
. We say C++ “truncates” or “rounds to zero” when it converts decimal numbers into integers.
One serious ramification of this problem is that multiplication and division are no longer true inverse operations, i.e. division and multiplication no longer cancel each other out all the time. For instance, if (a) and (b) are any two integers, one would (mathematically) expect the following equations to be true: \[\begin{align*} (a\times b) \div b = a && \textrm{and} && (a\div b)\times b = a.\end{align*}\] The first one will pretty much always be true, but the second may not.
1cout << (19 / 3) * 3 << endl;
This line of code will output 18
, not 19
!
This is where the modulo operator enters the fray: it represents the discrepancy in the equation \((a\div b) \times b = a\) in C++. More specifically, the expression a % b
will always be equal to a - (a / b) * b
for any integers a
and b
, so that the equation
a = (a / b) * b + a % b
is always true when a
and b
are integers in C++. When a
and b
are positive integers, a % b
coincides with the remainder of the integer division a / b
! But when a
or b
is negative, this above equation unambiguously determines the value of a % b
whereas the notion of a “remainder” may not be so clear.
Double Trouble
So what happens if you want \(6.333\ldots\) instead of \(6\) when you perform 19 / 3
?
Recall that decimal numbers are represented using the double
variable type in C++.
Remark 2. 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.
The most important thing to know about a double is that it has finite precision; we will see some examples of this later on.
Arithmetic operations involving at least one double will produce doubles:
1cout << 19 / 3 << endl;
2cout << 19.0 / 3 << endl;
3cout << 19 / 3.0 << endl;
4cout << 19.0 / 3.0 << endl;
Only the first expression is an integer; the latter 3 all were “promoted” to doubles, despite the presence of an integer.
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 3.
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
time
from the user. - Define the integer
ms = time * 1000
. - Define the integer
seconds = ms / 1000
and replacems
withms % 1000
. - Define the integer
minutes = seconds / 60
and replaceseconds
withseconds % 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 4. 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++.
Problem 5. Digits
Write a C++ program that asks a user for a number, then determines the third digit after the decimal point.
Sample run:
INPUT 30.3571
OUTPUT 7
INPUT -39.82113
OUTPUT 1
INPUT 5
OUTPUT 0