CS253 HW7: Ranges!                
Changes                
A line in the example code was wrong. Old bad line:
assert(Range(12,34)==a && s == "foobar");
new good line:
assert(Range<int>(12,34)==a && s == "foobar");
I don’t know why the old bad line compiled for me, but it’s
not required to work.
                
Description                
Frequently, in computation, it is useful to represent a range of
possible values: prices, ages, heart rates, etc. For this final
assignment, you write a templated class called Range
, with a capital
R
(no relation to C++20 std::ranges
). A Range
represents a
closed (inclusive) interval [a,b ]. For example,
Barack Obama was U.S. President [2009,2017],
because he was inaugurated on January 20, 2009, and served until
January 20, 2017.
                
A Range
contains two numbers (based on the template parameter),
representing the range. The first number must be ≤ the second number.
This writeup will use [a,b ] to represent a Range
,
but that’s just mathematical notation, and the input/output format.
You can’t actually have a statement r = [3,4]; // 🦡
                
Two Range
objects can be combined, through arithmetic operations.
The resulting Range
represents the limits of the results of the
computation. For example:
- [4,20] + [500,900] = [504,920]
- [80,90] − [1,6] = [74,89]
- [80,90] / [2,5] = [16,45]
- [5,7] * [-3,2] = [-21,14]
The first example means:
- Consider all the numbers 4,5,6,…,18,19,20.
- Consider all the numbers 500,501,502,…,898,899,900.
- Now, if we add together all possible combinations of those
sets, what’s the smallest possible result? It’s, 4+500, or 504.
- Similarly, the largest possible result is 20+900, or 920.
- Therefore, the resultant range is [504,920].
- It was easy with addition, because we just added the low numbers
and added the high numbers. It’s not always that easy.
The resulting Range
must be in order (first ≤ second).
                
This is not probability. This is not a bell-shaped curve. This is
a simple range of possibilities.
                
A Range
has a template parameter, which must be a numeric type,
either integer or floating-point. This type must be used for the
storage of the two values, and for all computations.
                
Methods and operations                
Range<T>
must have these public methods & operators:
                
-
Range(T val)
-
This constructor initializes both values of the
Range
to val.
-
Range(T low, T high)
-
This ctor initializes the values of the
Range
to low and
high. Throw a runtime_error object, mentioning both values, if
the range is out of order.
- copy ctor, assignment operator
-
Copy both values from the other object.
-
.assign(T low, T high)
-
Assign these values to the object.
Throw a runtime_error object, mentioning both values, if the range
is out of order.
-
.min()
- Return the first (lower) number.
-
.max()
- Return the second (higher) number.
-
Range1 += Range2
- Add
Range2
to Range1
.
-
Range1 -= Range2
- Subtract
Range2
from Range1
.
-
Range1 *= Range2
- Multiply
Range1
by Range2
.
-
Range1 /= Range2
- Divide
Range1
by Range2
.
If Range2
contains zero, then throw a runtime_error object,
mentioning the values in both objects.
The previous four methods put their result in Range1
,
and do not modify Range2
.
                
-
Range1 + Range2
- Add
Range2
to Range1
.
-
Range1 - Range2
- Subtract
Range2
from Range1
.
-
Range1 * Range2
- Multiply
Range1
by Range2
.
-
Range1 / Range2
- Divide
Range1
by Range2
.
If Range2
contains zero, then throw a runtime_error object,
mentioning the values in both objects.
The previous four methods do not modify either Range1
or Range2
.
Instead, they yield the result by value.
                
-
Range1 == Range2
-
Yield true iff the objects contain the same values.
-
Range1 != Range2
-
Yield false iff the objects contain the same values.
For the +=
, -=
, *=
, /=
, +
, -
, *
, /
,
==
, and !=
operators, the right-hand argument can also be a
number of type T
(the template parameter). This is treated as a
singleton Range
having both values of that number, as in the
single-argument ctor.
                
These two operators must be implemented as free functions:
                
-
ostream << Range
-
Write the
Range
to the ostream.
The <<
operator must yield a reference to the ostream.
This will write exactly six bytes
“[4,12]
” to standard output:
Range<unsigned> r(4,12);
cout << r;
-
istream >> Range
-
Read the
Range
from the istream, in the same format as output
above, with optional spaces before the brackets, numbers, and comma.
The >>
operator must yield a reference to the istream.
failbit must be set if invalid input is encountered.
The types and names in the descriptions, above, do not determine the
C++ declarations of those methods. They only serve to informally
describe what sort of arguments a method might take. For example,
==
should return a bool, even though it wasn’t explicitly
stated.
                
Const-correctness, for methods, arguments, and operators, is your job.
For example, it must be possible call .min()
on
a const Range
object, or compare two const objects.
                
You may define other methods or data, public or private, as you see fit.
                
Debugging                
If you encounter “STACK FRAME LINK OVERFLOW”, then try this:
export STACK_FRAME_LINK_OVERRIDE=ffff-ad921d60486366258809553a3db49a4a
This is the Colorado State University CS253 web page
https://cs.colostate.edu/~cs253/Spring22/HW7
fetched by unknown <unknown> with Linux UID 65535
at 2024-11-21T19:37:09 from IP address 3.137.178.122.
Registered CSU students are permitted to copy this web page for personal
use, but it is forbidden to repost the information from this web page to the
internet. Doing so is a violation of the rules in the CS253 syllabus,
will be considered cheating, and will get you an F in CS253.
Sample Run                
% cat CMakeLists.txt
cmake_minimum_required(VERSION 3.11)
project(hw7)
# Are we in the wrong directory?
if (CMAKE_SOURCE_DIR MATCHES "[Hh][Ww]([0-9])$"
AND NOT PROJECT_NAME MATCHES "${CMAKE_MATCH_1}$")
message(FATAL_ERROR "Building ${PROJECT_NAME} in ${CMAKE_SOURCE_DIR}")
endif()
# Using -Wall is required:
add_compile_options(-Wall)
# These compile flags are highly recommended, but not required:
add_compile_options(-Wextra -Wpedantic)
# Optional super-strict mode:
add_compile_options(-fmessage-length=80 -fno-diagnostics-show-option
-fstack-protector-all -g -O3 -std=c++17 -Walloc-zero -Walloca
-Wctor-dtor-privacy -Wduplicated-cond -Wduplicated-branches -Werror
-Wextra-semi -Wfatal-errors -Winit-self -Wlogical-op -Wold-style-cast
-Wshadow -Wunused-const-variable=1 -Wzero-as-null-pointer-constant)
# add_compile_options must be BEFORE add_executable.
add_executable(test test.cc)
# Create a tar file every time:
add_custom_target(${PROJECT_NAME}.tar ALL COMMAND
tar -cf ${PROJECT_NAME}.tar *.cc *.h CMakeLists.txt)
% cmake . && make
… cmake output appears here …
… make output appears here …
% cat test.cc
#include "Range.h"
#include <cassert>
#include <iostream>
#include <sstream>
using namespace std;
int main() {
Range<int> a(4,20), b(500,900), c(504,920), d(80,90), e(1,6), f(74,89);
const Range<unsigned short> g(80,90), h(2,5), i(16,45);
Range<long long> j(5,7), k(-3,2), l(-21,14);
const Range<long double> m(2.5,8.25);
const long double n = -0.5;
Range<long double> o(-4.125, -1.25);
const Range<int> range_containing_zero(-2,196607), zero(0);
auto i_result = a; i_result += b;
cout << a << " += " << b << " yields " << i_result << '\n';
assert(i_result == c);
i_result = d - e;
cout << d << " - " << e << " = " << i_result << '\n';
assert(i_result == f);
auto us_result = g / h;
cout << g << " / " << h << " = " << us_result << '\n';
assert(us_result == i);
auto ll_result = j; ll_result *= k;
cout << j << " *= " << k << " yields " << ll_result << '\n';
assert(ll_result == l);
auto ld_result = m; ld_result *= n;
cout << m << " *= " << n << " yields " << ld_result << '\n';
assert(ld_result == o);
assert(ld_result.min() == -4.125 && ld_result.max() == -1.25);
stringstream ss(" [ 12 \t ,34 \n ] foobar");
string s;
assert(ss >> a >> s);
assert(Range<int>(12,34)==a && s == "foobar");
bool caught = false;
try {
o.assign(5.001L, 4.999L); // out of order
}
catch (const runtime_error &re) {
cout << "Tried to assign out of order: " << re.what() << '\n';
caught = true;
}
assert(caught);
caught = false;
try {
f /= range_containing_zero;
}
catch (const runtime_error &re) {
cout << "Tried to divide by zero: " << re.what() << '\n';
caught = true;
}
assert(caught);
assert(zero / a == zero);
cout << "Done.\n";
}
static_assert(sizeof(Range<short>) == 2*sizeof(short));
static_assert(sizeof(Range<unsigned int>) == 2*sizeof(unsigned int));
static_assert(sizeof(Range<long double>) == 2*sizeof(long double));
% ./test
[4,20] += [500,900] yields [504,920]
[80,90] - [1,6] = [74,89]
[80,90] / [2,5] = [16,45]
[5,7] *= [-3,2] yields [-21,14]
[2.500000,8.250000] *= -0.5 yields [-4.125000,-1.250000]
Tried to assign out of order: out of order: [5.001000,4.999000]
Tried to divide by zero: divide by zero: [74,89]/[-2,196607]
Done.
Hints                
- Since you have the single-argument ctor, the operators taking a number
as the right-hand argument should just work without explicit
implementation. The number will get converted to a
Range
.
- My test program is inadequate for your testing.
Testing                
You will have to write a main() function to test your code. Put it in a
separate file. Particularly, do not put main() in Range.h
.
We will test your program by doing something like this:
                
mkdir new-directory
cd new-directory
tar -x </some/where/else/hw7.tar
cmake . && make
cp /some/other/place/test-program.cc .
g++ -Wall test-program.cc
./a.out
We will supply a main program to do the testing that we want. You
should do something similar. It’s your choice whether to include your
test program in your hw7.tar
file. However,
cmake . && make
must work. If it fails because you didn’t
package test.cc
, but your CMakeLists.txt
requires test.cc
,
then your build failed, and you get no points.
Test your tar file, not just your code.
                
Requirements                
- The results are undefined if:
- two
Range
objects with different template parameters are
combined arithmetically, assigned, or compared
- an arithmetic operation overflows, e.g., two
Range<int>
objects, each containing two billion, are added, since four billion
can’t be represented in our 32-bit int.
- You may add other methods and operators, as needed.
- You may implement the methods inside or outside of
the class declaration.
- It must be an compile-time error or warning to ignore the return value
of
.min()
, .max()
, ==
, or !=
,
- Your header file must have
#include
guards.
- You may not have any
using namespace
declarations in your
header files.
- You may not use any external programs. You many not use
system(), fork(), popen(), execl(), execvp(), etc.
- You may not use C-style I/O,
such as printf(), scanf(), fopen(), and getchar().
- You may not use endl. Use flush if needed.
- You may not use dynamic memory via new, delete,
malloc(), calloc(), realloc(), free(), strdup(), etc.
- It’s ok to implicitly use dynamic memory via containers
such as string or vector.
- You may not use the .eof() method.
- No global variables.
- For readability, don’t use ASCII int constants (
65
) instead of
char constants ('A'
) for printable characters.
- We will compile your code like this:
cmake . && make
- If that generates warnings, you will lose a point.
- If that generates errors, you will lose all points.
- There is no automated testing/pre-grading/re-grading.
- Test your code yourself. It’s your job.
- Even if you only change it a little bit.
- Even if all you do is add a comment.
- Test with the CSU compilers, not just your laptop’s compiler.
If you have any questions about the requirements, ask.
In the real world, your programming tasks will almost always be
vague and incompletely specified. Same here.
                
Tar file                
- The tar file for this assignment must be called:
hw7.tar
- It must contain:
- source files (
*.cc
), if any
Range.h
CMakeLists.txt
- These commands must execute without errors or warnings:
cmake . && make
- Your
CMakeLists.txt
must use at least -Wall
when compiling.
How to submit your work:                
In Canvas, check in the
file
hw7.tar
to the assignment “HW7”.
It’s due 11:59ᴘᴍ MT Saturday, with a five-day late period.
                
How to receive negative points:                
Turn in someone else’s work.