Class help please

Pages: 12
As an exercise I have to write a rational number class, show it in its reduced form, and be able to perform addition, subtraction, multiplication and division.
I know this involves operator overloading but I simply can't work it out...
Below is my code so far, please feel free to pick any holes in it - I'm new to this!!
I'm using xcode 7.3 if that helps.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
  //
//  main.cpp
//  exercise 9.6 Rational Class
//
//  Created by James Farrow on 02/09/2016.
//  Copyright © 2016 James Farrow. All rights reserved.
//

#include <iostream>
#include "RationalNumber.hpp"


int main() {
    
    RationalNumber object1;
    
    std::cout << "GCD is " << object1.getGreatestCommonDivisor() << std::endl;
    std::cout << "Rational number is: " << object1.getNumerator() << "\\" << object1.getDenomenator() << std::endl;
    std::cout << "In reduced form: " << (object1.getNumerator()/object1.getGreatestCommonDivisor());
    std::cout << "\\" << (object1.getDenomenator()/object1.getGreatestCommonDivisor()) << std::endl;
    std::cout << "\nIn decimal form: " << object1.getFloatingPointEquiv() << std::endl;
                                                                        
    
    return 0;
    
    
    }

//
//  Created by James Farrow on 04/09/2016.
//  Copyright © 2016 James Farrow. All rights reserved.
//

#include "RationalNumber.hpp"


RationalNumber::RationalNumber(){
    setNumerator();
    setDenomenator();
    setGreatestCommonDivisor(numerator, denomenator);
}

void RationalNumber::setNumerator(){
    std::cout << "Input your rational number's none zero numerater: ";
    std::cin >> numerator;
    while (! std::cin || numerator == 0) {
        std::cin.clear();
        std::cin.ignore();
        std::cout << "\nInput your rational number's none zero numerater: ";
        std::cin >> numerator;
    }
}

void RationalNumber::setDenomenator(){
    std::cout << "Input your rational number's none zero denomenater: ";
    std::cin >> denomenator;
    while (! std::cin || denomenator == 0) {
        std::cin.clear();
        std::cin.ignore();
        std::cout << "\nInput your rational number's none zero numerater: ";
        std::cin >> denomenator;}
}

int RationalNumber::getNumerator(){
    return numerator;
}

int RationalNumber::getDenomenator(){
    return denomenator;
}

int RationalNumber::getGreatestCommonDivisor(){
    return gcd;
}

void RationalNumber::setGreatestCommonDivisor(int a, int b){
    while ( b != 0 ){
        
        int temp1, temp2;
        
        temp1 = b;
        temp2 = a % b;
        a = temp1;
        b = temp2;
        gcd = a;
    }
}

double RationalNumber::getFloatingPointEquiv(){
    floatingPointEquivalent = static_cast<double>(numerator)/denomenator;
    return floatingPointEquivalent;
}

//
//  RationalNumber.hpp
//  exercise 9.6 Rational Class
//
//  Created by James Farrow on 04/09/2016.
//  Copyright © 2016 James Farrow. All rights reserved.
//

#ifndef RationalNumber_hpp
#define RationalNumber_hpp

#include <iostream>

class RationalNumber{
public:
    RationalNumber();
    void setNumerator();
    void setDenomenator();
    void setGreatestCommonDivisor( int numerator, int denomentor);
    int getNumerator();
    int getDenomenator();
    double getFloatingPointEquiv();
    int getGreatestCommonDivisor();
private:
    int numerator;
    int denomenator;
    double floatingPointEquivalent = 0;
    int gcd;
};

#endif /* RationalNumber_hpp */
EDIT: You spelled denominator incorrectly btw.

If you want to print the RationalNumber object in the fraction form, you can try overloading the << operator
1
2
3
4
5
std::ostream & operator<< ( std::ostream &out, const RationalNumber &rhs )
{
    out << rhs.getNumerator() << '/' << rhs.getDenomenator();
    return out; 
}


Then, you can print the fraction like this
1
2
3
RationalNumber object1;

std::cout << object1; // Print the fraction 


This is just my suggestion, but I would include a member function in RationalNumber class to actually simplify the function rather than doing this operation in the main() function (I'm talking about Line 19). It does not take much to make a function like
1
2
3
4
5
void RationalNumber::simplify()
{
    numerator /= gcd;
    denomenator /= gcd;
}


1
2
3
std::cout << "The Rational Number is " << object1;
object1.simplify();
std::cout << "The reduced form is " << object1;


Another suggestion is that you prompt user to enter the values for the numerator and the denominator in main() and THEN pass those values to the constructor or setter functions rather than prompting the user in the setter functions. In other words, leave I/O responsibility to the main function and let your RationalNumber class focus on maintaining the internal state.

As for overloading the arithmetic operators, check these out and see if you can use these for your RationalNumber class.
http://www.learncpp.com/cpp-tutorial/92-overloading-the-arithmetic-operators-using-friend-functions/
http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/arithmetic.html

Remember that adding or subtracting fractions requires that you find the least common denominator.
Last edited on
Ive added your overloaded function to my RationalNumber.cpp and I have the following error from xcode

Member function 'getNumerator' not viable: 'this' argument has type 'const RationalNumber', but function is not marked const

and from main I have

Invalid operands to binary expression ('basic_ostream<char, std::__1::char_traits<char> >' and 'RationalNumber')

Can you help please!!

Thanks James
> Member function 'getNumerator' not viable: 'this' argument has type 'const RationalNumber', but function is not marked const

It's because I have marked the object const when passing it as a parameter and I failed to notice that your getter functions were not marked const. I wanted to pass the object by reference instead of by value or copy to avoid expensive data copying. But if I pass the object by reference, I can possibly modify and corrupt the internal state so I passed the object as const reference ( Pass the address of the object for data access but prevent any changes).

As a result, inside the operator<<, you can only call const member functions on the rhs object.

You can fix that problem by adding "const" in your class declaration for your getter functions and also in the function definition header.

1
2
3
public:
    int getNumerator() const;
    int getDenomenator() const;


1
2
3
4
5
6
7
int RationalNumber::getNumerator() const {
    return numerator;
}

int RationalNumber::getDenomenator() const {
    return denomenator;
}


You may also fix the issue by changing the operator<< definition header and make it receive the object by copy.
1
2
3
4
5
std::ostream & operator<< ( std::ostream &out, RationalNumber rhs )
{
    out << rhs.getNumerator() << '/' << rhs.getDenomenator();
    return out; 
}


I really recommend that you fix the problem by marking your member functions const because usually it's good practice to identify which member functions are allowed to make modifications to the private data and which are not. Then, you put const next to the functions that are not making modifications to the internal data.


> Invalid operands to binary expression ('basic_ostream<char, std::__1::char_traits<char> >' and 'RationalNumber')

This is probably a result of the first error. So, try handling the first error and see if this error goes away.
Last edited on
Hi thanks for your help so far, I have followed your instruction ( I think) and I have the following:-

Invalid operands to binary expression ('basic_ostream<char, std::__1::char_traits<char> >' and 'RationalNumber')

Can you help again please!!!

Thanks James
I tested it on my machine and it compiles and runs just fine.
1
2
3
4
5
6
7
int main() {

    RationalNumber object1;
    std::cout << object1;

    return 0;
}


Input your rational number's none zero numerater: 3
Input your rational number's none zero denomenater: 5
3/5
Process returned 0 (0x0) execution time : 3.994 s
Press any key to continue.


Why don't you post your updated code, so I can take a look?

One thing I will suggest for now judging from the error message is that you include <iostream> wherever you place the overloaded operator<<. (For example, if you placed the overloaded function in RationalNumber.cpp, make sure you include <iostream> in that file).




EDIT: Also, if you placed the overloaded operator<< in the RationalNumber.cpp file, you have to include the function prototype either at the top of main.cpp or include it in the header file.
Last edited on
Here is my code so far. There must be something wrong somewhere!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//
//  main.cpp
//  exercise 9.6 Rational Class
//
//  Created by James Farrow on 02/09/2016.
//  Copyright © 2016 James Farrow. All rights reserved.
//

#include <iostream>
#include "RationalNumber.hpp"


int main() {
    
    RationalNumber object1;
    
    //std::cout << "GCD is " << object1.getGreatestCommonDivisor() << std::endl;
    
    
    //std::cout << "Rational number is: " << object1.getNumerator() << "\\" << object1.getDenomenator() << std::endl;
    //std::cout << "The rational number is: " << object1 ;
    
    
    std::cout << object1;
    
    
    //std::cout << "In reduced form: " << (object1.getNumerator()/object1.getGreatestCommonDivisor());
    //std::cout << "\\" << (object1.getDenomenator()/object1.getGreatestCommonDivisor()) << std::endl;
    //std::cout << "\nIn decimal form: " << object1.getFloatingPointEquiv() << std::endl;
                                                                        
    
    return 0;
    
    
    }

//
//  RationalNumber.cpp
//  exercise 9.6 Rational Class
//
//  Created by James Farrow on 04/09/2016.
//  Copyright © 2016 James Farrow. All rights reserved.
//

#include "RationalNumber.hpp"
#include <iostream>


RationalNumber::RationalNumber(){
    setNumerator();
    setDenomenator();
    setGreatestCommonDivisor(numerator, denomenator);
}

void RationalNumber::setNumerator(){
    std::cout << "Input your rational number's none zero numerater: ";
    std::cin >> numerator;
    while (! std::cin || numerator == 0) {
        std::cin.clear();
        std::cin.ignore();
        std::cout << "\nInput your rational number's none zero numerater: ";
        std::cin >> numerator;
    }
}

void RationalNumber::setDenomenator(){
    std::cout << "Input your rational number's none zero denomenater: ";
    std::cin >> denomenator;
    while (! std::cin || denomenator == 0) {
        std::cin.clear();
        std::cin.ignore();
        std::cout << "\nInput your rational number's none zero numerater: ";
        std::cin >> denomenator;}
}

void RationalNumber::simplify(){
    numerator /= gcd;
    denomenator /= gcd;
}

int RationalNumber::getNumerator()const {
    return numerator;
}

int RationalNumber::getDenomenator()const {
    return denomenator;
}

int RationalNumber::getGreatestCommonDivisor(){
    return gcd;
}

void RationalNumber::setGreatestCommonDivisor(int a, int b){
    while ( b != 0 ){
        
        int temp1, temp2;
        
        temp1 = b;
        temp2 = a % b;
        a = temp1;
        b = temp2;
        gcd = a;
    }
}

double RationalNumber::getFloatingPointEquiv(){
    floatingPointEquivalent = static_cast<double>(numerator)/denomenator;
    return floatingPointEquivalent;
}

std::ostream &operator<<( std::ostream &out, RationalNumber const &rhs )
{
    out << rhs.getNumerator() << '/' << rhs.getDenomenator();
    return out;
}


//
//  RationalNumber.hpp
//  exercise 9.6 Rational Class
//
//  Created by James Farrow on 04/09/2016.
//  Copyright © 2016 James Farrow. All rights reserved.
//

#ifndef RationalNumber_hpp
#define RationalNumber_hpp

#include <iostream>

class RationalNumber{
public:
    RationalNumber();
    void setNumerator();
    void setDenomenator();
    void setGreatestCommonDivisor( int numerator, int denomentor);
    void simplify();
    int getNumerator()const ;
    int getDenomenator()const ;
    double getFloatingPointEquiv();
    int getGreatestCommonDivisor();
private:
    int numerator;
    int denomenator;
    double floatingPointEquivalent = 0;
    int gcd;
};

#endif /* RationalNumber_hpp */

Thanks for your help so far!!!

James
Try this for me.

In your RationalNumber.hpp file, include the function prototype.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//
//  RationalNumber.hpp
//  exercise 9.6 Rational Class
//
//  Created by James Farrow on 04/09/2016.
//  Copyright © 2016 James Farrow. All rights reserved.
//

#ifndef RationalNumber_hpp
#define RationalNumber_hpp

#include <iostream>

class RationalNumber{
public:
    RationalNumber();
    void setNumerator();
    void setDenomenator();
    void setGreatestCommonDivisor( int numerator, int denomentor);
    void simplify();
    int getNumerator()const ;
    int getDenomenator()const ;
    double getFloatingPointEquiv();
    int getGreatestCommonDivisor();
private:
    int numerator;
    int denomenator;
    double floatingPointEquivalent = 0;
    int gcd;
};

std::ostream &operator<<( std::ostream &out, RationalNumber const &rhs );

#endif /* RationalNumber_hpp */ 
Hi Thanks again! I have solved it by putting the overloaded function prototype in main just below the header files.
Shouldn't this be included with the class heaser file instead?

Thanks again!

James
> Shouldn't this be included with the class heaser file instead?

Yes
Thanks very much!! Is it possible to do something similar with my double getFloatingPointEquiv() function? I also need to work out how to do the following as well...

abjetc1 + object2

James
> Is it possible to do something similar with my double getFloatingPointEquiv() function?

No, I don't know a way to overload operator<< to print either decimal or function, if there is one.
Who knows, if you wait around, someone with more experience may actually know a way or provide some brilliant workaround.

Personally, I thought getFloatingPointEquiv() was sufficient for its purpose. Maybe, if you change the name to something like getFloat(); you can easily print the equivalent floating-point value by simply coding
 
std::cout << object1.getFloat();

, which isn't too bad.



> I also need to work out how to do the following as well... abjetc1 + object2

As for overloading + operator and any other arithmetic operators such as - / *, you generally follow the format
 
ObjectType ObjectType::operator+ ( const ObjectType &object ) const;

This is a member function. Notice only 1 object being pass as a parameter? It's because this member function already has access to its own object. Notice that the function is marked const and so is the object being passed. When we perform binary arithmetic operations, we do not and should not expect to destroy or alter the objects(operands) in any way.

You can also make a non-member function by following this general format
 
ObjectType operator+ ( const ObjectType &lhs , const ObjectType &rhs );

Notice that 2 objects are being passed by const reference instead of just one. It makes sense because this is a non-member function and it does not have access to any class object, so it requires 2 class objects to be passed to perform the arithmetic operation.

Another way is declaring a friend function in the definition of the class
 
friend ObjectType operator+ ( const ObjectType &lhs , const ObjectType &rhs );

Think of this as a non-member function that is "friend" with the class and has access to its private variables. But, for our exercise here, we don't really need access to private variables, do we?

You might be asking: What's the difference? Member versus Nonmember.
As far as I can tell (Note that I'm not really an expert, so in my limited experience), it's a matter of preference. If you overload +-*/ as non-member, you need to pass both objects as parameters. If it's a member function, you only need to provide one object as a parameter. If you need to access private variables for any reason, you can use the friend function. Feel free to try all 3 methods.

Remember that function addition / subtraction requires you to find the least common multiple for the denominators. I will leave it to you to find out how to implement it.

Last edited on
Thanks very much - you have been a great help! I really appreciate it!
I will give it a go.
Watch this space...

Cheers!
I have noticed that in my constructors I put the set Numerator/Denomenator and GCD functions.
This means that I can't create an 'empty' object i.e.
object3 = object1 + object2

Rethink required!!

I think one constructor that accepts 2 values is sufficient for the purpose of this exercise.

1
2
3
4
5
6
RationalNumber::RationalNumber( int thatNumerator , int thatDenominator )
: numerator{ thatNumerator } , denomenator{ thatDenominator }
{
    assert( thatDenominator != 0 );    // Requires #include <cassert>
    setGreatestCommonDivisor(numerator, denomenator); 
}

This constructor accepts numerator and denominator and initializes the two variables. Then, it will set the gcd.

For the setter functions, you can do this
1
2
3
4
5
6
7
8
9
10
void RationalNumber::setNumerator( int thatNumerator )
{
    numerator = thatNumerator
}

void RationalNumber::setDenomenator( int thatDenominator )
{
    assert( thatDenominator != 0 );
    denomenator = thatDenominator;
}


You do not have to create any "empty" objects.
When you overload arithmetic operators, obtain the correct values for numerator and denominator and simply return
 
return RationalNumber{ numerator , denominator }; // This will create the object and return it 


You need to fix the operator<< by the way. If the numerator is a zero, it needs to just print 0. If both the numerator and denominator are negative values, it needs to print a positive fraction, not -n/-d (This is something I didn't think about earlier because we were just trying to get the operator to work first).
Last edited on
Thanks again! I have at least got the overloaded addition and subtraction working but code requires some tidying.
I will have a go at what you said above. Shouldn't I use assert for the numerator as well? I don't want that to be a zero?

Cheers!
> Shouldn't I use assert for the numerator as well? I don't want that to be a zero?

If you want to. I didn't suggest assert for numerator because 0 is a legal value for the numerator and 0 / d = 0 where d != 0.
Last edited on
A few thoughts.

First, use of assert as in this circumstance isn't really what it's meant for, though it really depends on what you want your function to do. The downside of assert is that if for some reason the denominator does end up as 0, the entire program will crash. There are a few options:

1) Leave the asserts, and understand that the program will crash if a 0 is provided. If you know that no 0s should be provided, ever, than this is acceptable, because it lets you know when something has gone wrong somewhere and you are receiving a 0 anyway. Also, this may be useful if the program crashing from invalid input is acceptable, though note that the error message provided could be nigh-incomprehensible to any users of your program.

2) Remove the asserts, and just hope you don't get a 0. This is fine, too; if someone provides a 0, your getFloatingPointEquiv function (or other functions dividing by the denominator) should just return inf, which might be an acceptable result.

3) Throw an exception. This lets the calling code know that something went wrong, and leaves it to them to determine what action should be undertaken (crash program, display error message and keep going, get new input and try again, etc.), though it is admittedly quite heavy-handed in this case.


As for operator overloading, the usual idiom is to provide a member operator+= or equivalent, and non-member operator+ in terms of operator+=. This way you hopefully avoid any surprises if you swap the order of the operands. For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A {
    public:
        // constructors and accessors
        A(int x) : x_{x} {}
        int val() const { return x; }

        // member operator+=
        A& operator+=(const A& other) { x_ += other.x_; }

    private:
        int x_;
};

// non-member operator+
A operator+(const A& lhs, const A& rhs) {
    A ret = lhs;
    ret += rhs;
    return ret;
}

In your code, it likely won't make a difference, but it's a good habit because doing binary operators as member functions can cause operands to work in one direction, but not the other. For example, for some imaginary type T with member operator+:
1
2
3
T a;
T b = a + 1; // works fine
T c = 1 + a; // breaks??? 
Thanks everyone for your help and input so far - it is greatly appreciated!

At the moment I am checking for Integers and zeroes :-
while ( !std::cin || numerator == 0){
Code here
}

It seems to work as I expected so far, if anything else other than a none zero integer is entered it tells you incorrect input and prompts fro correct input.

When I get chance I will try and tidy up my code and follow the help/pointers from the replies.
I will post my tidied code for anyone to pull apart!!!!

Cheers!
Pages: 12