Chapter 12 Exercise Set 1: Fractions¶
In Chapter 11 Exercise Set 0: Chapter Review you began creating a Fraction
object. In this mini
case study you will use TDD to more fully develop it.
First step is to write tests for the constructors of our new object.
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include <iostream> #include <string> #include <doctest.h> #include "Fraction.h" using namespace std; TEST_CASE("Test can create Fractions using two constructors") { Fraction f1; CHECK(f1.numerator == 0); CHECK(f1.denominator == 1); Fraction f2(3, 4); CHECK(f2.numerator == 3); CHECK(f2.denominator == 4); }
Add a third constructor that takes a string as an argument:
TEST_CASE("Test third Fraction constructor using a string") { Fraction f1("3/4"); CHECK(f1.numerator == 3); CHECK(f1.denominator == 4); Fraction f2("37/149"); CHECK(f2.numerator == 37); CHECK(f2.denominator == 149); }
Making this constructor work will be a great opportunity to revisit the string objects you studied in the Strings chapter. You could use find to locate the /, substr to extract the sequence of digits for the numerator and denominator, and then atoi to convert the strings into integers.
Add a
to_string()
function that will make the following tests pass.TEST_CASE("Test can render a Fraction as a string") { Fraction f1(17, 25); CHECK(f1.to_string() == "17/25"); Fraction f2(-7, 11); CHECK(f2.to_string() == "-7/11"); }
We are going to want to be able to use our
Fraction
s in all the ways we are accustomed to using mathematical objects. We will want to compare them to each other, and to perform the standard operation of addition, subtraction, multiplication and division with them. The question arises as to what we should do about equivalent fractions. How should we store6/8
? For the purpose of our case study here, we will store fractions in lowest terms, and we will add another test case to make that explicit.TEST_CASE("Test Fractions are stored in lowest terms") { Fraction f1(6, 8); CHECK(f1.to_string() == "3/4"); Fraction f2(8, 16); CHECK(f2.to_string() == "1/2"); }
To make these tests pass, we can use a helper function that computes the GCD. The Euclidean algorithm provides a perfect solution. But before we can implement it though, we have to write some tests!
TEST_CASE("Test gcd function") { CHECK(gcd(4, 14) == 2); CHECK(gcd(16, 12) == 4); CHECK(gcd(18, 27) == 9); }
The
gcd
function can be a stand-alone function defined inside theFraction.cpp
, with a function prototype inFraction.h
.How should we render fractions that have a denominator of
1
? Let’s render them in the standard way for integer values. We make our intent clear by adding a test:TEST_CASE("Test integer Fractions render properly") { Fraction f1(5, 1); CHECK(f1.to_string() == "5"); Fraction f2(18, 3); CHECK(f2.to_string() == "6"); }
While we are dealing with fractions that are integers, why not add another constructor to handle creating a fraction from an integer?
TEST_CASE("Test can construct Fraction from integer") { Fraction f1(5); CHECK(f1.numerator == 5); CHECK(f1.denominator == 1); Fraction f1(42); CHECK(f1.numerator == 42); CHECK(f1.denominator == 1); }
We already have a constructor that takes a string. Let’s make that handle the integer case as well.
TEST_CASE("Test string constructor with integers") { Fraction f1("15"); CHECK(f1.numerator == 15); CHECK(f1.denominator == 1); Fraction f2("42"); CHECK(f2.numerator == 42); CHECK(f2.denominator == 1); }
Now let’s overload all the comparison operators,
==
,>
,<
,>=
,<=
, and!=
.TEST_CASE("Test comparison operators for Fractions") { Fraction f1(1, 4); Fraction f2(3, 4); Fraction f3(2, 5); Fraction f4(6, 8); CHECK((f2 > f1) == true); CHECK((f2 == f4) == true); CHECK((f1 < f3) == true); CHECK((f3 != f2) == true); CHECK((f4 >= f1) == true); CHECK((f4 <= f2) == true); }
To finish this mini case study, we need to add functions to overload the
+
,-
,*
, and/
operators. It’s your turn to drive the testing process. Add new test cases to thetest_functions.cpp
for each of these operations, then write code to make the tests pass. Start by supporting these operations onFraction
objects only.After you have finished that, research how you could support operations involving a
Fraction
object with anint
.Have fun!