Rational Number Class

Alex Edwards 0 Tallied Votes 919 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";
}

Dani AI

Generated

Solid learning implementation and a good starting point. put the important pieces in place (reduction, arithmetic, I/O), and 's parser idea and 's note about low-level tricks show useful alternatives — but there are a few correctness, safety and API improvements worth applying before using this class in real code.

Key correctness / robustness fixes to do first:

  • Reject or handle a zero denominator in the constructor (throw std::invalid_argument or normalize to 1). Silent zero denominators lead to UB later.
  • Keep a canonical form: always store the denominator > 0 and move sign to the numerator. That makes comparisons and streaming simpler.
  • Avoid calling abs on raw int without thought: abs(INT_MIN) is undefined. Use unsigned/long types or portable helpers, or prefer std::gcd on non-negative values.
  • Mark value-query methods const (arithmetic operators, invert, comparisons) and return bool (not const bool).

Numeric safety and performance:

  • Reduce before multiplying to avoid overflow. Cross-cancel using two gcds: gcd(numer, other.den) and gcd(other.numer, den) and divide those out before doing the * — this drastically lowers overflow risk.
  • For addition/subtraction compute the common denominator via lcm = d1 / gcd(d1,d2) * d2 (use gcd first to avoid overflow), then form numerators with scaled factors and reduce the result.
  • Consider using a wider integer type (int64_t) or a bigint library for production uses where inputs can be large.

API and ergonomics:

  • Make the prefix ++x the primary implementation (modify and return *this) and implement postfix x++ by copying the old value then calling the prefix — that avoids two copies.
  • Improve operator>> to accept a/b text (read a token, split on '/', validate, then parse ints) so inputs like 3/4 behave as users expect.
  • For converting double->Rational use a continued-fraction or bounded-denominator approximation (not naive scaling) to control precision and avoid huge integers.
  • If you want a drop-in, well-tested solution consider using Boost.Rational rather than rolling your own for production code.

These changes keep the class small and easy to read while fixing correctness and making it safe for more realistic inputs.

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.