

A Rational Class
Note:
This assignment is intended to give you some
exposure to writing a relatively simple C++
class. In particular, it is intended to show you
how much the C++ compiler is doing for you
automatically — sometimes it does what you want,
and sometimes not.
Rational Numbers
Rational numbers are those numbers that can be represented as
ratios of integers:
1/2 57/234 2/3 23/405
(Note that all rational numbers are real numbers, but not all
real numbers are, or can be, rational numbers.)
The Rational Class
Implement a class Rational, which supports rational
numbers. The class should support the following:
 Two int data members, numerator and denominator, which
are private to the class. These values will be kept in normalized
form; that is, the value of the denominator will be the
smallest integer that can represent the ratio. For
example, 3/6 is not normalized; 1/2 is: 3/6 can be
normalized by dividing both top and bottom by 3, to
produce 1/2. If the Rational is negative, then
only the numerator is negative — the denominator is
always positive. Clearly, the denominator can never be
allowed to be zero.
 Initialization of a class instance from a single int. The
int will be the numerator of the ratio.
 Initialization of a class instance from two ints. The two
ints will be the numerator and the denominator of the
ratio.
 Instance cleanup (if necessary; if not necessary, explain
why not)
 Initialization of a class instance from another instance
of class Rational.
 Assignment of one Rational to another (how is this
different from the above?)
 Assignment of an int to a Rational.
 Assignment of a Rational to a double.
 Assignment of a double to a Rational should be prohibited
(remember, mathematically, not all real numbers are
rational numbers).
 The addition of two Rationals (Rational +
Rational) to produce another Rational.
 The addition of a Rational and a double (Rational
+ double) to produce a double.
 The addition of a double and a Rational (double +
Rational) to produce a double.
 The subtraction of two Rationals (Rational 
Rational) to produce another Rational.
 The subtraction of a double from a Rational
(Rational – double) to produce a double.
 The subtraction of a Rational from a double
(double – Rational) to produce a double.
 The multiplication of two Rationals (Rational *
Rational) to produce another Rational.
 The multiplication of a Rational with a double
(Rational * double) to produce a double.
 The multiplication of a double with a Rational
(double * Rational) to produce a double.
 The division of two Rationals (Rational /
Rational) to produce a Rational.
 The division of a Rational by a double (Rational /
double) to produce a double.
 The division of a double by a Rational (double /
Rational) to produce a double.
 The negation (unary minus) of a Rational
(Rational) to produce another Rational.
 The unary plus of a Rational (+Rational) to
produce another Rational.
 A Print() member function that prints out the value of a Rational
in its normalized form:
(3/4) (including the parentheses)
Strategy
It is very tempting to implement the above by writing all the
constructors, methods, operators, etc. from the start. However,
you may be surprised how much will work ‘magically’! It is not
necessary to map each of the above operations into a specific
class method (or normal function, as the case may be). I strongly
suggest that you take a minimalist approach: start with the
smallest class you can, and work from there, incrementally. See
how much that minimal class already supports, and understand
why and how it supports it. Then add what you need, as you
discover what doesn’t work yet. As you add, keep trying to
understand how the new stuff works (or why it doesn’t work), and
how things change as you add that functionality. Use this as
a learning experience!
Here’s a suggested approach (Yes, I do want to see
the answers to the questions!):
 Create, in file rational.h,
a class Rational, with private data members
numerator and denominator, and a private member function Normalize(), which does basic
checking of numerator and denominator values, and reduces
the ratio to its normal form (i.e. numerator and
denominator contain the smallest possible integer
values).
Write a public constructor to construct a Rational
from two ints, with
appropriate default arguments, and a public member
function Print(), to
print out the Rational as described earlier (Print() will help with
debugging…). In addition, create two public access
member functions, Numerator() and Denominator(). Here’s
the class to start off with:
// rational.h
class Rational
{
public:
// Constructor
Rational(int n = 0, int d = 1) <implement here>
// Print out the contents of the instance
void Print(); // Implemented in the rational.cpp file
// Access member functions
int Numerator() { return m_num; }
int Denominator() { return m_den; }
private:
// Private functions
void Normalize(); // To be implemented in the rational.cpp file
// Private data
int m_num; // numerator
int m_den; // denominator
};

Your job is to fill in the <implement
here>, and implement the Print() and Normalize() functions in the rational.cpp file.
Once you have completed this step, you should be able to
do simple construction of Rational types. Try this out
by writing a separate testrat.cpp
file, which will include the rational.h
file, and by linking the two objects produced from compiling
the rational.cpp and the testrat.cpp files.
 Without adding anything else to your Rational
class, try defining an instance of class Rational,
initializing it from another instance of class Rational.
Does it work? If so, can you explain why?
Now (again without adding anything to your class), try assigning
one instance of class Rational to another. Again,
does it work? If so, why?
 What about assigning other types to an instance
of class Rational? Try assigning an int (like the integer constant 3) to a Rational. Does
it work? If so, why, and is it reasonably efficient? If
not, how would you make it more efficient?
What about assigning a double
(like 45.6) to a Rational?
Does it work? Should it work? NO! In
general, a double value
cannot be expressed mathematically as an exact rational
number. So how would you prevent this assignment from
working?
 Now try assigning a Rational to a
double. Does it work? Why?/Why not? What will you do
about it?
Once you have double = Rational
assignment working, try implementing the unary minus and
unary plus operators:
Rational
+Rational
What should the type of this expression be? Does it work?
What do you need to make it work?
 Now, try adding the binary (that is, 2operand)
arithmetic operations. Start with addition. First look
at:
Rational + Rational
What should the type of this expression be? Does it work?
What do you need to make it work?
Once you have Rational+Rational
working, try:
Rational + double
What should the type of this expression be? What in fact
happens, and why? How do you make this work?
Once you have Rational+double
working, try reversing the order of the types:
double + Rational
What should the type of this expression be? Can you
explain what you observe? What do you have to do to make this
work?
Now implement subtraction, multiplication
and division for each of these combinations of types.
 Make sure that a call to a constructor Rational(double, double) — for
example:
Rational r(3.0, 5.1);
(or any other case where such a constructor might be
called, explicitly or implicity) — fails
at compile time.
 At this point, most operations should be
working. Once you have cleaned up any that are not, you
now have three things to do:
 Check that each operation is indeed working,
by thorough testing.
 Understand completely how each operation
works.
 Determine whether each operation works
sufficiently accurately
(no undue loss of precision) and efficiently
(no undue loss of performance).
How to Normalize
You will find that the following code for evaluating the greatest
common divisor (GCD) of two integers will help you normalize
your Rational class instances. I expect you to turn this code
into a private member function of class Rational, called Normalize(). The function Normalize()takes no arguments. Here is
some code that implements a greatest common divisor
function. Change the gcd()
function so that it becomes a member function of the class –
when you make it a member function of the class, can you
eliminate the m and n parameters?.
#ifndef GCD_H
#define GCD_H
//
// gcd.h
//
extern int gcd(int m, int n);
#endif

//
// gcd.cpp
//
// Compute the greatest common divisor
// of two integers, m and n.
// (Uses Euclid's algorithm)
//
#include "gcd.h"
int gcd(int m, int n)
{
if ( (m == 0)  (n == 0) )
return 1;
if (m < 0)
m = m;
if (n < 0)
n = n;
// Euclid's algorithm...
int a, b, r;
if (n > m)
{
a = n;
b = m;
}
else
{
a = m;
b = n;
}
r = 1;
while (r > 0)
{
r = a % b;
a = b;
b = r;
}
return a;
}
#ifdef TESTING
// This code is included for testing purposes.
// Simply define the macro TESTING, and recompile this module;
// it will be a standalone program that tests the above function.
// Once you have completed the testing, undefine the macro TESTING,
// and recompile.
//
// NOTE: Most C++ compilers provide a way of defining a macro at
// compile time. If you are using a compiler from the command
// line, there is usually a switch of some kind (D ?) to support
// this. If you are using an IDE (Interactive Development
// Environment), you will have to learn how to define a macro at
// compile time from within the IDE.
#include <iostream.h>
static void print_gcd(int m, int n)
{
cout << "GCD(" << m << ',' << n ") = " << gcd(m, n) << 'n';
}
int main()
{
print_gcd(5, 4);
print_gcd(10, 20);
print_gcd(3, 9);
print_gcd(121, 99);
return 0;
}
#endif // TESTING

Testing your Program
The following program should work correctly with your Rational
class implementation. Test your class against this program, and
verify the results against those expected.
//
// testrat.cpp
//
#include <iostream.h>
#include "rational.h"
int main()
{
cout << endl << "====Check constructor with two ints:";
cout << endl << "Rational a(3,4);";
Rational a(3,4);
cout << endl << "a: ";
a.Print();
cout << endl << "====Check constructor with one int:";
cout << endl << "Rational b(3);";
Rational b(3);
cout << endl << "b: ";
b.Print();
cout << endl << "====Check copy constructor:";
cout << endl << "Rational c(b);";
Rational c(b);
cout << endl <<"c: ";
c.Print();
cout << endl << "====Check Rational = Rational assignment:";
cout << endl << "a = b;";
a = b;
cout << endl << "a: ";
a.Print();
cout << endl << "====Check Rational = int assignment:";
cout << endl << "a = 1;";
a = 1;
cout << endl << "a: ";
a.Print();
#ifdef CHECK_ERRORS // Define CHECK_ERRORS to test this
cout << endl << "====Check Rational = double assignment:"
" (Should not work!):";
cout << endl << "a = 1.0;";
a = 1.0; // Should not compile
cout << endl << "a: ";
a.Print();
#endif
cout << endl << "====Check double = Rational assignment:";
cout << endl << "x = Rational(3,4);";
double x;
x = Rational(3,4);
cout << endl << "x: " << x;
cout << endl << "====Check Rational + Rational addition:";
cout << endl << "c = Rational(3,4) + Rational(1,4);";
c = Rational(3,4) + Rational(1,4);
cout << endl << "c: ";
c.Print();
cout << endl << "====Check double + Rational addition:";
cout << endl << "x = 37.25 + Rational(3,4);";
x = 37.25 + Rational(3,4);
cout << endl << "x: " << x;
cout << endl << "====Check Rational + double addition:";
cout << endl << "x = Rational(3,4) + 37.25;";
x = Rational(3,4) + 37.25;
cout << endl << "x: " << x;
cout << endl << "====Check Rational  Rational subtraction:";
cout << endl << "c = Rational(3,4)  Rational(1,4);";
c = Rational(3,4)  Rational(1,4);
cout << endl << "c: ";
c.Print();
cout << endl << "====Check double  Rational subtraction:";
cout << endl << "x = 37.25  Rational(3,4);";
x = 37.25  Rational(3,4);
cout << endl << "x: " << x;
cout << endl << "====Check Rational  double subtraction:";
cout << endl << "x = Rational(3,4)  37.25;";
x = Rational(3,4)  37.25;
cout << endl << "x: " << x;
cout << endl << "====Check Rational * Rational multiplication:";
cout << endl << "c = Rational(3,4) * Rational(1,4);";
c = Rational(3,4) * Rational(1,4);
cout << endl << "c: ";
c.Print();
cout << endl << "====Check double * Rational multiplication:";
cout << endl << "x = 37.25 * Rational(3,4);";
x = 37.25 * Rational(3,4);
cout << endl << "x: " << x;
cout << endl << "====Check Rational * double multiplication:";
cout << endl << "x = Rational(3,4) * 37.25;";
x = Rational(3,4) * 37.25;
cout << endl << "x: " << x;
cout << endl << "====Check Rational / Rational division:";
cout << endl << "c = Rational(3,4) / Rational(1,4);";
c = Rational(3,4) / Rational(1,4);
cout << endl << "c: ";
c.Print();
cout << endl << "====Check double / Rational division:";
cout << endl << "x = 37.25 / Rational(3,4);";
x = 37.25 / Rational(3,4);
cout << endl << "x: " << x;
cout << endl << "====Check Rational / double division:";
cout << endl << "x = Rational(3,4) / 37.25;";
x = Rational(3,4) / 37.25;
cout << endl << "x: " << x;
cout << endl << "====Check Rational negation:";
cout << endl << "c = Rational(3,4);";
c = Rational(3,4);
cout << endl << "c: ";
c.Print();
cout << endl << "====Check Rational unary plus:";
cout << endl << "c = +Rational(3,4);";
c = +Rational(3,4);
cout << endl << "c: ";
c.Print();
cout << endl << "====Destructors about to be checked...n";
return 0;
}

