/******************************************* * * Rational.cc * * Brent Heeringa * 22 Decemeber 2000 * * * Implementation of Rational numbers and associated basic operations. * I've added a number of lengthy comments (maybe too lengthy) at * spots where I think things aren't obvious, or where generally * it's a good idea to comment code. */ /* These are preprocessor directives. Like C, and unlike Java, a program preprocesses the file and does things like macro substitution and file replacement. The #ifndef tells the preprocessor that if the symbol __RATIONAL_CXX__ has not been defined, then processing should continue. Of course, the next statement tells the preprocessor to define the symbol __RATIONAL_CXX__. This is a bit over-protective in implemenation files, but on the whole, not a bad idea. It's more important in header files where variables and functions are declared. It is generally a good idea to not include implementation files. */ #ifndef __RATIONAL_CXX__ #define __RATIONAL_CXX__ /* The preprocessor literally substitutes the Rational.h file here. In Java, the import statement grants access to a namespace. While C++ does now have namespaces, they are seldom used. The probably should be used more. */ #include "Rational.h" /* Make sure we know about DivideByZeroExceptions */ #include "DivideByZeroException.h" /* The angle brackets tell the preprocessor that the math.h file is located in a system-defined directory. Hence, when compiling it is usually not necessary to tell the compiler were such files are loacted on the file-system. */ #include /* Make sure we know about the Greatest Common Divisor function. */ #include "Cmpsci197cUtil.h" /* Make sure we know about the absolute value function abs(int) */ #include /** * Default Constructor * Creates a rational number with value 1 * * Here we make a call to init() which is a * private initialization function for the Rational * class. The initialization routine is common to a number * of functions, so we simply abstracted it out. It is * computationally more inexpensive to write the constructor * as: * * Rational::Rational() : _numerator(1), denominator(1) { } * * This has the advantage of initializing the member variables * without calling the assignment operator. * * Notice also that we prefix the function name with the class name * (e.g., Rational::someFunction() ). This resolves the scope of the * function */ Rational::Rational() { init(1,1); } /** * Creates a rational number equivalent * to the integer of value numerator */ Rational::Rational(int numerator) { init(numerator,1); } /** * Creates a rational number with the given * numerator and denominator */ Rational::Rational(int numerator, int denominator) { init(numerator,denominator); if (denominator == 0) { DivideByZeroException dbze; throw(dbze); } } /** * Copy Constructor. * This would be generated automatically * by the compiler if */ Rational::Rational(const Rational& r) { init(r.getNumerator(), r.getDenominator()); } /** * Group initialization function */ void Rational::init(int numerator, int denominator) { _numerator = numerator; _denominator = denominator; normalize(); } /** * The normalize function moves the sign to numerator * and removes common divisors from the numerator and * denominator */ void Rational::normalize() { if (_denominator < 0) { _numerator = -_numerator; _denominator = -_denominator; } // the return of gcd may be negative, so we make sure // we send only positive numbers to the function int theGCD = gcd(abs(_numerator), abs(_denominator)); _numerator = _numerator / theGCD; _denominator = _denominator / theGCD; } /** * Assignment operator * * This is some ugly stuff. In Java, assignment is done by * passing references around. So really it's just about copying * memory addresses. This is (basically) equivalent to pointer * assignment in C++. If I were to write * * Rational* r1 = new Rational(); * Rational* r2 = r1; * * r2 would simply point to r1. If I altered r1, then r2 would * be altered as well. In Java it's the same thing. If you actually * want r2 to be a copy of r1, then you have to use the clone() method. * The assignment operator (along with the copy constructor) essentially * constitute the clonse() method in C++ * * The goal here is to make ourselves (this) look just like * the parameter r. All we need to do is set out numerator * and denominator to the same values as r, and we're finished. */ Rational& Rational::operator=(const Rational& r) { init(r.getNumerator(), r.getDenominator()); return(*this); } /** * Exponentiation function */ Rational Rational::power(int power) const { int numerator; int denominator; if (power < 0) { numerator = _denominator; denominator = _numerator; power *= -1; } else { numerator = _numerator; denominator = _denominator; } // on some platforms, the function "pow" returns a double // so we perform a cast to insure no warnings during compilation return(Rational((int) pow(numerator,power), (int) pow(denominator,power))); } /** * Accessor to the numerator */ inline int Rational::getNumerator() const { return(_numerator); } /** * Accessor to the denominator */ inline int Rational::getDenominator() const { return(_denominator); } /** * Pretty Prints a rational number * * We return a reference to the stream so that it's * possible syntactically to write statements such as * Rational r1; * cout << r << "\tFOO\n" << endl; * * Rational r2; * cout << r2++ << r2++ << r2++ << endl; * We would see: * * (3,0)(2,0)(1,0) * * and r would have final value (4,0) (because of the post-decrement) * * There's a lot going on here, but don't worry, we'll go over it * later in class. For now, you should be able to copy this function * and mutate it so that it writes the real and imaginary components * of a complex number. * */ ostream& operator<<(ostream& out, const Rational& r) { out << "(" << r.getNumerator() << "," << r.getDenominator() << ")"; return(out); } /** * Adding two rational numbers a/b and c/d is defined as * (a*d + c*b) / (b*d) * * Since operator+ was defined as a friend in the Rational.cc file * we can access the private member variables of the class. This isn't * really necessary here (as the other operator declarations show) since * we can access both the numerator and denominator through member functions. * Furthermore, in this situation it isn't a performance boost because * the numerator and denominator accessor functions are declared inline. * */ const Rational operator+(const Rational& r1, const Rational& r2) { return(Rational((r1._numerator * r2._denominator) + (r2._numerator * r1._denominator), r1._denominator * r2._denominator)); } /** * Subtracting two rational numbers a/b and c/d is defined as * (a*d - c*b) / (b*d) * or * r1 + -1*r2 (where r1 = a/b and r2 = c/d) */ const Rational operator-(const Rational& r1, const Rational& r2) { return(r1 + (Rational(-1) * r2)); } /** * Multiplying two rational numbers a/b and c/d is defined as * a*c / b*d */ const Rational operator*(const Rational& r1, const Rational& r2) { return(Rational(r1.getNumerator() * r2.getNumerator(), r1.getDenominator() * r2.getDenominator())); } /** * Dividing Rational numbers a/b and c/d is defined as * a*d / b*c * or * r1 * r2^(-1) (mulitiplication of the inverse) * where r1 = a/b and r2 = c/d */ const Rational operator/(const Rational& r1, const Rational& r2) throw (DivideByZeroException) { Rational divisor = r2.power(-1); if (divisor.getDenominator() == 0) { DivideByZeroException dbze; throw(dbze); } return(r1*divisor); } /** * Two rationals a/b and c/d are equal when * a*d = b*c */ const bool operator==(const Rational& r1, const Rational& r2) { return(r1.getNumerator() * r2.getDenominator() == r1.getDenominator() * r2.getNumerator()); } /** * Prefix uniary plus operation * * Prefix operations side-effect the object * and return the new value. Contrast this * with the postfix operator * */ const Rational& Rational::operator++() { _numerator += _denominator; normalize(); return(*this); } /** * Postfix uniary plus operation * * The purpose of this operator is to * increment the rational number by 1 * but return the value of the rational * before that increment occurs * */ const Rational Rational::operator++(int) { // store the old value Rational store = Rational(*this); // perform the update _numerator += _denominator; // normalize it normalize(); // return the stored rational number return(store); } /** * The less than operator */ const bool operator<(const Rational& r1, const Rational& r2) { return(r1.getNumerator() * r2.getDenominator() < r1.getDenominator() * r2.getNumerator()); } /** * The less than or equal operator. * Note that because we've already implemented the equal and * less than operators, we can simply reuse those functions */ const bool operator<=(const Rational& r1, const Rational& r2) { return((r1 < r2) || (r1 == r2)); } /** * The greater than operator (defined in terms of <=) */ const bool operator>(const Rational& r1, const Rational& r2) { return(r2 <= r1); } /** * The greater than or equal opreator (defined in terms of <) */ const bool operator>=(const Rational& r1, const Rational& r2) { return(r2 < r1); } #endif // __RATIONAL_CXX__