// Bjarne Stroustrup 7/25/2009 // Chapter 9 Exercise 13 /* Design and implement a rational number class with the usual arithmetic operations, etc. */ #include "std_lib_facilities.h" /* A rational number is represented by a numerator and a denominator. Its value is numerator/denominator. You may want to loop up the definition of rational aithmetic in a book (or on the web). For what we need here, the math is (easy) high school level. Design decisions: 1. allow direct access to the representation (num,den) 2. try to keep rational numbers "normalized", that is with the smallest possible denominator (e.g. 5/3 rather than 15/9); that requires a gcd (greatest common denominator) function. 3. don't try to make the code very fast; "simple and not too inefficient" is the ideal (to keep the code simple) 4. I won't check for overflow (to keep the code simple) */ int gcd(int x, int y) // greatest common denominator // Euclid's algorithm (using remainder) { x = abs(x); // don't get confused by negative values y = abs(y); while (y) { int t = y; y = x%y; x = t; } return x; } //--- the class ---------------------------------------------------------------- class Rational { public: Rational(int n, int d) :num(n), den(d) { normalize(); } // Rational(int n) :num(n), den(1) { } // deleted because I kept writing things like Rational(24/8) Rational() :num(0), den(1) { } void normalize() // keep denominator positive and minimal { if (den==0) error("negative denominator"); if (den<0) { den = -den; num = -num; } int n = gcd(num,den); if (n>1) { num/=n; den/=n; } } int num, den; }; //--- utilities ---------------------------------------------------------------- ostream& operator<<(ostream& os, Rational x) { return cout << '(' << x.num << '/' << x.den << ')'; } bool operator==(Rational x1, Rational x2) { return x1.num*x2.den==x1.den*x2.num; } bool operator!=(Rational x1, Rational x2) { return !(x1==x2); } double to_double(Rational x) // convert to double (to floating point representation) { return double(x.num)/x.den; } //----- arithmetic operations -------------------------------------------------------- Rational operator+(Rational x1, Rational x2) { Rational r(x1.num*x2.den+x1.den*x2.num, x1.den*x2.den); r.normalize(); return r; } Rational operator-(Rational x1, Rational x2) { Rational r(x1.num*x2.den-x1.den*x2.num, x1.den*x2.den); r.normalize(); return r; } Rational operator-(Rational x) // unary minus { return Rational(-x.num,x.den); } Rational operator*(Rational x1, Rational x2) { Rational r(x1.num*x2.num,x1.den*x2.den); r.normalize(); return r; } Rational operator/(Rational x1, Rational x2) { Rational r(x1.num*x2.den,x1.den*x2.num); r.normalize(); return r; } /* How do we test all that? The simplest is to compute a number of values using Rational and double and see if we get roughly the same results; if so we are not far off. That's a variant of the time honored and often elegant technique of calculating something in two different ways and then comparing the results. What you see below is *not* exhaustive, thorough, or sufficient testing. Please try some more. */ int main() try { Rational r1(4,2); cout << r1 << "==" << to_double(r1) << '\n'; Rational r2(42,24); cout << r2 << "==" << to_double(r2) <<'\n'; cout << r1+r2 << "==" << to_double(r1+r2) << "==" << to_double(r1)+to_double(r2) << '\n'; cout << r1-r2 << "==" << to_double(r1-r2) << "==" << to_double(r1)-to_double(r2) << '\n'; cout << -r2 << "==" << to_double(-r2) << "==" << -to_double(r2) << '\n'; cout << r1*r2 << "==" << to_double(r1*r2) << "==" << to_double(r1)*to_double(r2) << '\n'; cout << r1/r2 << "==" << to_double(r1/r2) << "==" << to_double(r1)/to_double(r2) << '\n'; if (r2==Rational(14,8)) cout << "equal\n"; if (r2!=Rational(14,8)) cout << "not equal\n"; Rational(3,0); // we're out of here! keep_window_open("~"); // For some Windows(tm) setups } catch (runtime_error e) { // this code is to produce error messages; it will be described in Chapter 5 cout << e.what() << '\n'; keep_window_open("~"); // For some Windows(tm) setups } catch (...) { // this code is to produce error messages; it will be described in Chapter 5 cout << "exiting\n"; keep_window_open("~"); // For some Windows(tm) setups } /* Why would anyone use rational numbers? Well, floating point is imprecise (e.g. can't represent a third exactly) and some things are for good and/or legal reasons required to be exact (e.g. financial calculations). */