CS 235 Unit 13 Exercise: Operator overloading

Table of Contents


1. Introduction

About
For this exercise you'll be implementing a Fraction class as well as unit tests for your Fraction class. A set of unit tests are provided to help you verify your logic.
Goals
  • Practice working with overloading the following operator types:
    • Arithmetic
    • Relational
    • Assignment
    • Streams
Setup
Download the starter code here: https://gitlab.com/moosadee/courses/-/tree/main/wip_exercises/starter_code/c3_u13_OperatorOverloading?ref_type=heads

Files included:

.
├── Fraction.cpp
├── Fraction.h
├── main.cpp
├── Program.cpp
├── Program.h
└── tester
    ├── Fraction_Tester.cpp
    ├── Fraction_Tester.hpp
    ├── Helper.cpp
    ├── Helper.hpp
    ├── TesterBase.cpp
    └── TesterBase.hpp

2. The Fraction program

--------------------------------------------------------------------------------
 | MAIN MENU |
 -------------

Fraction 0: 1/1 (1.000)
Fraction 1: 1/1 (1.000)

OPTIONS

 0.	Quit program
 1.	Run unit tests
 2.	Initialize fraction
 3.	Arithmetic
 4.	Relational

This program allows the user to set two fractions and then utilize the arithmetic and relational operators on those fractions. From the main menu, you can also run option #1 to run the unit tests. These will be used to help you verify that your code works properly.

Once the Fraction is implemented, you’ll be able to run various functionality to test manually:

--------------------------------------------------------------------------------
 | INITIALIZATION |
 ------------------

Fraction 0: 1/1 (1.000)
Fraction 1: 1/1 (1.000)

Update which fraction? #
 >> 0

Enter new fraction with NUMERATOR DENOMINATOR
Make sure to separate the numerator/denominator with a SPACE
>> 1 2

Fraction is now: 1/2

----------------------------------------
 | ARITHMETIC |
 --------------

Fraction 0: 1/2 (0.500)
Fraction 1: 4/5 (0.800)

0. Go back
1. Add      1/2 + 4/5
2. Subtract 1/2 - 4/5
3. Multiply 1/2 * 4/5
4. Divide   1/2 / 4/5
Perform which operation?

 >> 1

1/2 + 4/5 = 13/10

----------------------------------------
 | RELATIONAL |
 --------------

Fraction 0: 1/2 (0.500)
Fraction 1: 4/5 (0.800)

0. Go back
1. Is 1/2 == 4/5?
2. Is 1/2 != 4/5?
3. Is 1/2 <= 4/5?
4. Is 1/2 >= 4/5?
3. Is 1/2 <  4/5?
4. Is 1/2 >  4/5?

Perform which operation?

 >> 1

1/2 == 4/5 is FALSE

3. Running the unit tests

c3_u13_OperatorOverloading_Tester.png

After running the unit tests view the testresult.html file that is located wherever your project file is – such as vcxproj for Visual Studio or cbp for Code::Blocks.

After implementing each function in the Fraction class, you should be running the unit tests to verify your work for that item.

Test driven development is a style of software development where tests are created and implemented before the thing it's testing is implemented. There are some benefits to writing tests first:

  • Ever write an essay and try to proof read it and miss mistakes because your brain fills it in? Well if you write the Fraction class first, your brain will do this while you're writing the tests - you'll forget certain things to look for.
  • Writing test cases first helps you think of what are the requirements for the functionality and helps you document that before doing any coding.

(That being said, none of the companies I've worked at have used TDD, though most places I've worked have required programmers to write unit tests to test their code after the fact.)

Having tests in general helps with…

  • Making sure that your logic works as intended.
  • Checking for edge cases / error cases.
  • As you keep working on new features, make sure you aren't breaking old features (old tests don't fail? Yay!)

In the Tester class, there is a function to test each of the functions available in the Fraction class. We will implement tests for all functionality to ensure we have complete coverage to validate the Fraction class.


4. The Fraction class

class Fraction
{
public:
  Fraction();

  float GetDecimal() const;

  Fraction CommonDenominatorize( const Fraction& other ) const;

  // Operator overloading
  // Assignment operator
  Fraction& operator=( const Fraction& other );

  // Arithmetic operators
  friend Fraction operator+( const Fraction& left, const Fraction& right );
  friend Fraction operator-( const Fraction& left, const Fraction& right );
  friend Fraction operator*( const Fraction& left, const Fraction& right );
  friend Fraction operator/( const Fraction& left, const Fraction& right );

  // Relational operators
  friend bool operator==( const Fraction& left, const Fraction& right );
  friend bool operator!=( const Fraction& left, const Fraction& right );
  friend bool operator<=( const Fraction& left, const Fraction& right );
  friend bool operator>=( const Fraction& left, const Fraction& right );
  friend bool operator<( const Fraction& left, const Fraction& right );
  friend bool operator>( const Fraction& left, const Fraction& right );

  // Stream operators
  friend ostream& operator<<( ostream& out, const Fraction& fraction );
  friend istream& operator>>( istream& in, Fraction& fraction );

  friend class Tester;

private:
  int m_num;
  int m_denom;
};

The Fraction class contains a m_num and m_denom as member variables. Beyond that it’s all functionality. The assignment operator = is used to assign a value from one Fraction to the local ("this") Fraction. It is the only operator that we will be implementing that belongs to the class itself.

Arithmetic operators allow us to add, subtract, multiply, and divide two fractions together, the relational operators allow us to compare two fractions, and the stream operators let us input and output data from our fraction. These functions are classified as friends as they are not member functions! They are independent functions, but as a friend the function will have access to a Fraction’s m_num and m_denom, which are normally private.


4.1. float Fraction::GetDecimal() const

This function is responsible for returning a decimal version of a fraction, such as 0.5 for \(\frac{1}{2}\). To do this, you need to divide the numerator and denominator.

The result of two integers being divided will be an integer result, which gets rid of our decimal point.

Make sure to CAST one of the values to a float in order to get the correct result: float( m_num )


4.2. Fraction Fraction::CommonDenominatorize( const Fraction& other ) const

We're going to lazily get the common denominator by multiplying FractionA's numerator by FractionB's denominator, and multiply FractionA's denominator by FractionB's denominator. (That's not a typo, remember that d/d = 1).

  1. Check to see if m_denom is equivalent to other.m_denom – if this is the case, then just return *this instead.
  2. Otherwise:
    1. Create a new Fraction to store the result.
    2. Assign the Fraction to the result of *this fraction x other.denom/other.denom.
    3. Return the result Fraction.

4.3. Fraction& Fraction::operator=( const Fraction& other )

  1. Error check: If THIS item's address is the same as the rhs address then just return *this (don't try to change the data): = if ( this = &other ) { return *this; }
  2. Otherwise, set the numerator to the =other=’s numerator.
  3. Set the denominator to the =other=’s denominator.

4.4. Fraction operator*( const Fraction& left, const Fraction& right )

The result of the left fraction times the right fraction, in a Fraction form.

\[\frac{n_L}{d_L} \cdot \frac{n_R}{d_R} = \frac{n_L \cdot n_R}{d_L \cdot d_R}\]

  • \(n_L\) is "left numerator"
  • \(d_L\) is "left denominator"
  • \(n_R\) is "right numerator"
  • \(n_L\) is "left numerator"

c3_u13_OperatorOverloading_Multiply.png


4.5. Fraction operator/( const Fraction& left, const Fraction& right )

The result of the left fraction divided by the right fraction, in a Fraction form.

\[\frac{n_L}{d_L} \div \frac{n_R}{d_R} = \frac{n_L \cdot d_R}{d_L \cdot n_R}\]

  • \(n_L\) is "left numerator"
  • \(d_L\) is "left denominator"
  • \(n_R\) is "right numerator"
  • \(n_L\) is "left numerator"

c3_u13_OperatorOverloading_Division.png


4.6. Fraction operator+( const Fraction& left, const Fraction& right )

When adding two fractions together we need to get a common denominator. After we have a common denominator, the solution will share this same denominator, and the numerator will be the left numerator plus the right numerator.

Steps:

A. Original fractions: \[\frac{n_L}{d_L} + \frac{n_R}{d_R}\]

B. Create a new version of the left fraction with the common denominator. \[\frac{n_1}{d_1} = \frac{n_L \cdot d_R}{d_L \cdot d_R}\] Fraction commonLeft = left.CommonDenominatorize( right )

C. Create a new version of the right fraction with the common denominator. \[\frac{n_2}{d_2} = \frac{n_R \cdot d_L}{d_R \cdot d_L}\] Fraction commonRight = right.CommonDenominatorize( left )

D. The result is then: \[\frac{n_1+n_2}{d_L \cdot d_R}\]


4.7. Fraction operator-( const Fraction& left, const Fraction& right )

Steps A, B, and C are the same as with addition.

D. The result is then: \[\frac{n_1-n_2}{d_L \cdot d_R}\]


4.8. bool operator==( const Fraction& left, const Fraction& right )

Returned output: true if the two fractions are the same (\(\frac{1}{2}\) and \(\frac{2}{4}\) are the same), false otherwise.

Steps:

A. We have two fractions, left and right: \(\frac{n_L}{d_L} = \frac{n_R}{d_R}\) ??

B. Create a new version of the left fraction with the common denominator. \[\frac{n_1}{d_1} = \frac{n_L \cdot d_R}{d_L \cdot d_R}\] Fraction commonLeft = left.CommonDenominatorize( right )

C. Create a new version of the right fraction with the common denominator. \[\frac{n_2}{d_2} = \frac{n_R \cdot d_L}{d_R \cdot d_L}\] Fraction commonRight = right.CommonDenominatorize( left )

D. If the numerators are the same and the denominators are the same, then return true, or false otherwise.


4.9. bool operator!=( const Fraction& left, const Fraction& right )

Returned output: true if the two fractions are NOT the same (\(\frac{1}{2}\) and \(\frac{2}{4}\) are the same), false otherwise.

Steps:

  • If == returns true, return false instead.
  • If == returns false, return true instead.

You can just return the result of !( a == b).


4.10. bool operator<=( const Fraction& left, const Fraction& right )

Returned output: true if the left fraction is less than the right fraction (1/2 is less than 3/4) or equal to (1/2 is equal to 2/4), false otherwise.

Create two common denominatorized Fractions again (like with ==) and then compare the numerators: Return the result of commonLeft.mnum <= commonRight.mnum.


4.11. bool operator>=( const Fraction& left, const Fraction& right )

Returned output: true if the left fraction is greater than the right fraction (3/4 is greater than 1/2) or equal to (1/2 is equal to 2/4), false otherwise.

Create two common denominatorized Fractions again (like with ==) and then compare the numerators: Return the result of commonLeft.mnum <= commonRight.mnum.


4.12. bool operator<( const Fraction& left, const Fraction& right )

Returned output: true if the left fraction is less than the right fraction (1/2 is less than 3/4), false otherwise.

Common denominatorize both fractions, compare the numerators.


4.13. bool operator>( const Fraction& left, const Fraction& right )

Returned output: true if the left fraction is greater than the right fraction (3/4 is greater than 1/2), false otherwise.

Common denominatorize both fractions, compare the numerators.


4.14. ostream& operator<<( ostream& out, const Fraction& fraction )

Output the numerator and the denominator to the out stream, formatted in a way that is clearly a fraction, like "1/2". (Then return the out stream back out.)

ostream& operator<<( ostream& out, const Fraction& fraction )
{
  out << fraction.m_num << "/" << fraction.m_denom;
  return out;
}

4.15. istream& operator>>( istream& in, Fraction& fraction )

Input the numerator and then the denominator via the in input stream. (Then return the in stream back out.)

istream& operator>>( istream& in, Fraction& fraction )
{
  in >> fraction.m_num >> fraction.m_denom;
  return in;
}

Once all the unit tests run and pass, you can reasonably assume that your Fraction logic is correct. (I like to think that I’m not a terrible test writer…)


Author: Rachel Wil Sha Singh

Created: 2023-10-01 Sun 00:43

Validate