Rational Number Class

Alex Edwards 0 Tallied Votes 869 Views Share

Provides a means of performing operations on Rational numbers that can be treated as fractions to make non-whole numbers easier to calculate and read than typical floating point storage types.

This was a project for a class. This is by no means an attempt to "reinvent the wheel" (The reason I say this is because I'm fairly certain the Boost libraries have an implementation of this class)

/**
 * Rational.h
 */

#pragma once
#include <iostream>

using std::ostream;
using std::istream;

typedef int Rnumber;

class Rational{
private:
	Rnumber numerator;
	Rnumber denominator;
	void reduce(Rnumber&, Rnumber&);
	Rnumber gcd(Rnumber, Rnumber);

	public:
		Rational(Rnumber = 0, Rnumber = 1);
		Rational invert() const;
		Rational operator++(int);
		Rational& operator++();
		Rational operator--(int);
		Rational& operator--();
		Rational& operator=(const Rational&);
		Rational operator+(const Rational&);
		Rational operator*(const Rational&);
		Rational operator-(const Rational&);
		Rational operator/(const Rational&);
		const bool operator<(const Rational&);
		const bool operator<=(const Rational&);
		const bool operator>(const Rational&);
		const bool operator>=(const Rational&);
		const bool operator==(const Rational&);
		friend ostream& operator << (ostream&, const Rational&);
		friend istream& operator >> (istream&, Rational&);
};


/////////////////////////////


/**
 * Rational.cpp
 */

#include "Rational.h"
#include <cmath>

/**
 * Constructor for Rational objects
 */
Rational::Rational(Rnumber lhs, Rnumber rhs){
	
	/*Make temporary values for the Rational numbers*/
	Rnumber x = lhs;
	Rnumber y = rhs;

	reduce(x, y); // reduce the Temporary values
	
	/*Store the reduced values obtained from the function reduce into the global variables*/
	this->numerator = x;
	this->denominator = y;
}

/**
 * Creates an inverted copy of this Rational object
 */
Rational Rational::invert() const{
	return Rational(this->denominator, this->numerator);
}

/**
 * Reduces the numerator and denominator to the smallest possible factors of the orignal value.
 */
void Rational::reduce(Rnumber& lhs, Rnumber& rhs){
	
	/*A number is either negative or not - storing the state of negativity in bools for numerator and denominator*/
	bool leftIsNegative = abs(lhs) != lhs;
	bool rightIsNegative = abs(rhs) != rhs;

	/*For simplicity I'm only going to work with positive numbers*/
	Rnumber tempLeft = abs(lhs);
	Rnumber tempRight = abs(rhs);

	/*The lazy way of determining factors of numbers that can reduce the Rational*/
	/*
	for(int i = tempLeft; i > 1; i--){
		if(tempLeft%i != 0) continue; // dont evaluate unnecessary values to boost performance

		for(int j = tempRight; j > 1; j--){
			if( ((tempLeft%i) == 0 && (tempRight%i) == 0 && (tempRight%j) == 0 ) ){
				tempLeft = static_cast<int>(static_cast<double>(tempLeft)/i);
				tempRight = static_cast<int>(static_cast<double>(tempRight)/i);
				i = tempLeft + 1;
				j = tempRight + 1;
			}
		}
	}*/

	/*Using the Euclidian algorithm to find the greatest common divisor between the numbers*/
	Rnumber divisor = this->gcd(tempLeft, tempRight);
	tempLeft /= divisor;
	tempRight /= divisor;

	/*Providing appropriate negative-number notion for the reduced number result*/
	if( (leftIsNegative && (!rightIsNegative)) 
	 || ((!leftIsNegative) && rightIsNegative ))
	 tempLeft *= -1;

	/*Assigning the reduced values to the ones originally stored*/
	lhs = tempLeft;
	rhs = tempRight;
}

/**
 * This function returns the greatest common divisor between two numbers
 * (Thanks Ron - this is a lot more efficient than my original algorithm!)
 */
Rnumber Rational::gcd(Rnumber lhs, Rnumber rhs){

	/*
		Uses the division method to determine the greatest common divisor.
		Example:

		lhs = 5, rhs = 7

		-gcd(5, 7) -> gcd(7, 5%7) -> gcd(7, 5)
		-gcd(7, 5) -> gcd(5, 7%5) -> gcd(5, 2)
		-gcd(5, 2) -> gcd(2, 5%2) -> gcd(2, 1)
		-gcd(2, 1) -> gcd(1, 2%1) -> gcd(1, 0)
		-gcd returns 1 because rhs = 0
	 */
   return ( rhs != 0 ? gcd(rhs, lhs % rhs) : lhs ); 
}

/**
 * Increments this Rational by one and returns the previous version of this object before
 * the increment [Postfix version].
 */
Rational Rational::operator++(int){
	Rational temp = *this;
	(*this) = Rational( this->denominator + this->numerator, this->denominator );
	return temp;
}

/**
 * Immediately increments this object by one [Prefix version].
 */
Rational& Rational::operator++(){
	(*this)++;
	return *this;
}

/**
 * Decrements this Rational by one and returns the previous version of this object before
 * the decrement [Postfix version].
 */
Rational Rational::operator--(int){
	Rational temp = *this;
	(*this) = Rational( this->numerator - this->denominator, this->denominator );
	return temp;
}

/**
 * Immediately decrements this object by one [Prefix version].
 */
Rational& Rational::operator--(){
	(*this)--;
	return *this;
}

/**
 * Overloading the equals operator for Rational objects.
 * Simply assigns the numerator and denominator of the argument Rational
 * to the numerator and denominator of this rational.
 */
Rational& Rational::operator=(const Rational& other){
	this->numerator = other.numerator;
	this->denominator = other.denominator;
	return *this;
}

/**
 * The sum of this Rational and the argument Rational
 */
Rational Rational::operator+(const Rational& other){

	/*
	 * This section of code needs an explanation.
	 * Suppose there are two Rationals, 3/4 and 5/6
	 * In order to perform addition properly, it is best to 
	 * convert each Rational in an expanded form, such that
	 * the denominators are the same.
	 *
	 * 3/4 + 5/6 would expand to be 18/24 + 20/24
	 * The sum of the two rationals would be the sum of the new numerators
	 * divided by...
	 * the average of the value of denominators (which would just be the
	 * new denominator)
	 */
	Rnumber leftN = this->numerator * other.denominator;
	Rnumber leftD = this->denominator * other.denominator;
	Rnumber rightN = this->denominator * other.numerator;
	Rnumber rightD = this->denominator * other.denominator;

	Rnumber resultNumerator = leftN + rightN;
	Rnumber resultDenominator = static_cast<int>(abs(leftD)); // both leftD and RightD are the same anyways

	return Rational(resultNumerator, resultDenominator);
}

/**
 * The product of this Rational and the other Rational
 */
Rational Rational::operator*(const Rational& other){

	return Rational( this->numerator * other.numerator, 
					 this->denominator * other.denominator);
}

/**
 * The difference of this Rational and the argument Rational
 */
Rational Rational::operator-(const Rational& other){

	/*
	 * This section of code needs an explanation.
	 * Suppose there are two Rationals, 3/4 and 5/6
	 * In order to perform addition properly, it is best to 
	 * convert each Rational in an expanded form, such that
	 * the denominators are the same.
	 *
	 * 3/4 - 5/6 would expand to be 18/24 - 20/24
	 * The difference of the two rationals would be the difference of the new numerators
	 * divided by...
	 * the average of the value of denominators (which would just be the
	 * new denominator)
	 */
	Rnumber leftN = this->numerator * other.denominator;
	Rnumber leftD = this->denominator * other.denominator;
	Rnumber rightN = this->denominator * other.numerator;
	Rnumber rightD = this->denominator * other.denominator;

	Rnumber resultNumerator = leftN - rightN;
	Rnumber resultDenominator = static_cast<int>(abs(leftD)); // both leftD and RightD are the same anyways

	return Rational(resultNumerator, resultDenominator);
}

/**
 * The quotient of this Rational and the other Rational
 */
Rational Rational::operator/(const Rational& other){

	return (*this) * other.invert();
}

/**
 * Tests is this Rational is less than the argument Rational
 */
const bool Rational::operator<(const Rational& other){
	return (this->numerator * other.denominator) < (other.numerator * this->denominator);
}

/**
 * Tests is this Rational is less than or equal to the argument Rational
 */
const bool Rational::operator<=(const Rational& other){
	return ( (*this) < other ) || ( (*this) == other );
}

/**
 * Tests is this Rational is greater than the argument Rational
 */
const bool Rational::operator>(const Rational& other){
	//return (this->numerator * other.denominator) > (other.numerator * this->denominator);
	return !( (*this) <= other ); // Will only be true when this Rational is greater than the other
}

/**
 * Tests is this Rational is greater than or equal to the argument Rational
 */
const bool Rational::operator>=(const Rational& other){
	return !( (*this) < other ); // Will only be true when this Rational is greater than or equal to the other
}

/**
 * Tests for equivilance between two Rational objects.
 */
const bool Rational::operator==(const Rational& other){
	return (this->numerator * other.denominator) == (this->denominator * other.numerator);
}

/**
 * Allows the binary data contained within this Rational object to be
 * easily pushed into an ostream type (which can by a file output stream,
 * console output stream, application output stream, query output stream,
 * Network output stream, etc).
 */
ostream& operator << (ostream& out, const Rational& rat){
	return out << rat.numerator << "/" << rat.denominator;
}

/**
 * Allows for a buffer with information stored inside it to write the binary data it contains
 * into a data type stored in the argument Rational object.
 */
istream& operator >> (istream& in, Rational& rat){
	Rnumber a = 0, b = 0;
	in >> a >> b;
	Rational temp (a, b);
	rat = temp;
	return in;
}

//////////////////////////////////


/**
 * Alex Edwards
 * 10/23/2008
 * Project 2 : Rational
 */
#include <iostream>
#include "Rational.h"

const char* getBoolName(bool);

using std::cin;
using std::cout;
using std::endl;

/**
 * Entry point of the program.
 */
int main(){
	
	Rational a;
	Rational b;
	cout << "Enter the numerator and denominator for Rational a: ";
	cin >> a;
	cout << "\nEnter the numerator and denominator for Rational b: ";
	cin >> b;
	Rational s = (a + b);
	Rational d = (a - b);
	Rational q = (a / b);
	Rational p = (a * b);
	cout << "\nResults... \n" << endl;
	cout << "Sum of a and b       : \t" << s << endl;
	cout << "Difference of a and b: \t" << d << endl;
	cout << "Quotient of a and b  : \t" << q << endl;
	cout << "Product of a and b   : \t" << p << endl;
	cout << "a < b : \t\t" << getBoolName((a < b)) << endl;
	cout << "a <= b: \t\t" << getBoolName((a <= b)) << endl;
	cout << "a == b: \t\t" << getBoolName((a == b)) << endl;
	cout << "a >= b: \t\t" << getBoolName((a >= b)) << endl;
	cout << "a > b : \t\t" << getBoolName((a > b)) << endl;
	cout << "a++   : \t\t" << (a++, a) << endl;
	cout << "b++   : \t\t" << (b++, b) << endl;
	cout << "a--   : \t\t" << (a--, a) << endl;
	cout << "b--   : \t\t" << (b--, b) << endl;
	cout << "++a   : \t\t" << (++a) << endl;
	cout << "++b   : \t\t" << (++b) << endl;
	cout << "--a   : \t\t" << (--a) << endl;
	cout << "--b   : \t\t" << (--b) << endl;
	cin.ignore(INT_MAX, '\n');
	cin.get();
	return 0;
}

/**
 * Convenience method for returning the stringified name of a boolean result.
 */
const char* getBoolName(bool arg){
	return (arg) ? "True" : "False";
}
William Hemsworth 1,339 Posting Virtuoso

:) I once made practically the exact same thing, though I did it just for practice ;) Though, this looks much better than the one I made.
And hey, it helped me with my maths :D

Alex Edwards 321 Posting Shark

As expected from you XP

And thanks! =)

But yeah... I'm planning on extending this class, but I'm not sure of how to do it.

For example, I want to make this class compatible with doubles so I'm thinking of making a double/double constructor, but then I'd have to worry about precision factors to convert the double into a big number, then use the gcd on it to convert it into an integer/integer value.

Maybe sometime later though @_@

William Hemsworth 1,339 Posting Virtuoso

Weeee, I found it :) See how much better yours looks :]?
And, thats weird, I don't even remember adding a parser to it ;)

#include <iostream>
#include <cmath>

class fraction {
   // Returns true if text contains ch
   static inline bool TextContains(char *text, char ch) {
      while (*text) {
         if (*text++ == ch) {
            return true;
         }
      }
      return false;
   }

   static inline void SubStr(char *text, char *target, int beg, int end) {
      int len = end - beg;
      memcpy_s(target, (rsize_t)len, &text[beg], (size_t)len);
      target[len] = '\0';
   }

   static void GetWord(char *text, char *target, char *gaps, int bzIndex) {
      int i = 0, sc = 0, ec = 0, g = 0;
      for (; text[i] && TextContains(gaps,text[i]); i++);
      for (sc = i; text[i]; i++) {
         if (TextContains(gaps,text[i])) {
            while (text[i] && TextContains(gaps,text[i + 1])) i++;
            if (++g == bzIndex) sc = i + 1;
         } else if (g == bzIndex) {
            while(text[i] && !TextContains(gaps, text[i])) i++;
            ec = i;
            break;
         }
      }
      SubStr(text, target, sc, ec);
   };

public:
   int Numerator;
   int Denominator;
   int count; // Whole numbers

   fraction() {
      count = 0;
      Numerator = 0;
      Denominator = 1;
   }

   fraction(int v) {
      count = v;
      Numerator = 0;
      Denominator = 1;
   }

   fraction(int v, int n, int d) {
      Numerator = n;
      Denominator = d;

      if (Denominator == 0) {
         Numerator = 0;
         Denominator = 1;
      }

      count = (Numerator > Denominator ? 
               Numerator / Denominator : 0) + v;

      if (count != 0) {
         Numerator -= (Numerator / Denominator) * Denominator;
      }

      Shrink();
   }

   fraction(int n, int d) {
      Numerator = n;
      Denominator = d;

      if (Denominator == 0) {
         Numerator = 0;
         Denominator = 1;
      }

      count =  Numerator > Denominator ? 
               Numerator / Denominator : 0;

      if (count != 0) {
         Numerator -= (Numerator / Denominator) * Denominator;
      }

      Shrink();
   }

   fraction(double fraction2) {
      int count = (int) fraction2;
      unsigned char decimalCount = 0;
      int den = 1;

      while (ceil(fraction2) != fraction2) {
         decimalCount++;
         fraction2 *= 10;
         den *= 10;
      }

      fraction2 -= (count * den);
      if (den == 0) den = 1;
      *this = fraction(count, (int) fraction2, den);
      Shrink();
   }

   fraction(char *str) {
      Parse(str);
   }

   inline bool Shrink() {
      bool sucessful = 0;
      for (int i = std::min(Numerator, Denominator); i; i--) {
         if ((Numerator % i == 0) && (Denominator % i == 0)) {
            Numerator /= i;
            Denominator /= i;
            sucessful = 1;
         }
      }
      return sucessful;
   }

   fraction operator =(fraction &fraction2) {
      count = fraction2.count;
      Denominator = fraction2.Denominator;
      Numerator = fraction2.Numerator;
      return *this;
   }

   fraction operator =(int fraction2) {
      count = fraction2;
      Denominator = 1;
      Numerator = 0;
      return *this;
   }

   fraction operator =(double fraction2) {
      *this = fraction(fraction2);
   }

   fraction operator =(char *str) {
      Parse(str);
   }

   fraction operator +(fraction &fraction2) {
      int n1 = Numerator + (count * Denominator);
      int d1 = Denominator;
      int n2 = fraction2.Numerator + (fraction2.count * fraction2.Denominator);
      int d2 = fraction2.Denominator;
      int MatchedDenominator = d1 * d2;
      fraction f(
         ((MatchedDenominator / d1) * n1) + 
         ((MatchedDenominator / d2) * n2)
         ,MatchedDenominator);
      f.Shrink();
      return f;
   }

   fraction operator -(fraction &fraction2) {
      int n1 = Numerator + (count * Denominator);
      int d1 = Denominator;
      int n2 = fraction2.Numerator;
      int d2 = fraction2.Denominator + (fraction2.count * fraction2.Denominator);
      int MatchedDenominator = d1 * d2;
      fraction f(
         ((MatchedDenominator / d1) * n1) - 
         ((MatchedDenominator / d2) * n2)
         ,MatchedDenominator);
      f.Shrink();
      return f;
   }

   fraction operator *(fraction &fraction2) {
      int n1 = Numerator + (count * Denominator);
      int d1 = Denominator;
      int n2 = fraction2.Numerator + (fraction2.count * fraction2.Denominator);
      int d2 = fraction2.Denominator;
      fraction f(n1 * n2, d1 * d2);
      f.Shrink();
      return f;
   }

   fraction operator /(fraction &fraction2) {
      int n1 = Numerator + (count * Denominator);
      int d1 = Denominator;
      int n2 = fraction2.Numerator + (fraction2.count * fraction2.Denominator);
      int d2 = fraction2.Denominator;
      fraction f(n1 * d2, d1 * n2);
      f.Shrink();
      return f;
   }

   /* Overload operators with double */
   fraction operator +(double fraction2) {
      return (*this + fraction(fraction2));
   }

   fraction operator -(double fraction2) {
      return (*this - fraction(fraction2));
   }

   fraction operator *(double fraction2) {
      return (*this * fraction(fraction2));
   }

   fraction operator /(double fraction2) {
      return (*this / fraction(fraction2));
   }

   /*   +=  -=  /=  *=    */
   fraction operator +=(double fraction2) {
      *this = *this + fraction(fraction2);
      return *this;
   }

   fraction operator -=(double fraction2) {
      *this = *this - fraction(fraction2);
      return *this;
   }

   fraction operator *=(double fraction2) {
      *this = *this * fraction(fraction2);
      return *this;
   }

   fraction operator /=(double fraction2) {
      *this = *this / fraction(fraction2);
      return *this;
   }

   inline double Value() {
      return (((double)Numerator+((double)count*(double)Denominator)) / (double)Denominator);
   }

   void Parse(char *str) {
      size_t c = 0; // '/' count
      for (register int i = 0; str[i]; i++, c += (str[i] == '/'));

      if (c == 1) {
         char temp1[5], temp2[5];

         GetWord(str, temp1, " /", 0);
         GetWord(str, temp2, " /", 1);

         fraction fract(
            atoi(temp1),
            atoi(temp2)
            );

         *this = fract;
      } // 1
      else if (c == 2) {
         char temp1[5], temp2[5], temp3[5];

         GetWord(str, temp1, " /", 0);
         GetWord(str, temp2, " /", 1);
         GetWord(str, temp3, " /", 2);

         fraction fract(
            atoi(temp1),
            atoi(temp2),
            atoi(temp3)
            );

         *this = fract;
      } // 2
      else *this = 0;
   }

#ifdef _CONSOLE
   void Display() {
      if (Numerator != 0) {
         if (count != 0) {
            std::cout << count << '/' << Numerator << '/' << Denominator;
         } else {
            std::cout << Numerator << '/' << Denominator;
         }
      } else {
         std::cout << count;
      }
   }

   friend std::ostream& operator <<(std::ostream& out, fraction &f) {
      f.Display();
      return out;
   }

   friend std::istream& operator >>(std::istream& in, fraction &f) {
      char str[25];
      std::cin.getline(str,25);
      size_t c = 0; // '/' count

      for (int i = 0; str[i]; i++, c += (str[i] == '/'));

      if (c == 1) {
         char temp1[5], temp2[5];

         GetWord(str, temp1, " /", 0);
         GetWord(str, temp2, " /", 0);

         fraction fract(
            atoi(temp1),
            atoi(temp2)
            );

         f = fract;
      } // 1

      else if (c == 2) {
         char temp1[5], temp2[5], temp3[5];

         GetWord(str, temp1, " /", 0);
         GetWord(str, temp2, " /", 0);
         GetWord(str, temp3, " /", 0);

         fraction fract(
            atoi(temp1),
            atoi(temp2),
            atoi(temp3)
            );

         f = fract;
      } // 2
      else f = 0;
      return in;
   }
#endif
};

int main() {

   fraction f = "3/33/99";
   // 3/33/99 --> 3/1/3 --> 3.3333

   f *= 2.5; // 3.3333 * 2.5 = 8.33325
   f /= 67; // 8.33325 / 67 = 0.12437

   std::cout << f; // 25/201  (0.12437)
   std::cin.ignore();
   return 0;
}
MosaicFuneral 812 Nearly a Posting Virtuoso

There's also the FPU-machine instruction: fistp Probably not portable to every platform, but simple. Agner Fog has an example of it in his Asmlib, and a bunch of other cool asm-to-C++ functions.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.