4. Week 2 Tuesday: Trouble with Doubles
≪ 3. Week 1 Thursday: Integer Operations | Table of ContentsRecall that when dividing two integers, C++ will always truncate the answer and produce another integer. We saw that this has its applications last week, but there are definitely scenarios in which you would prefer to just work with the decimal values instead. These are double
types rather than int
types.
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
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 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.