Rationale Zahlen: Die Klasse Fraction

1. Aufgabe

In dieser Übung wollen wir uns der Klasse Fraction in einer zusammenfassenden Betrachtung annehmen. Folgenden Eigenschaften sollen in Ihrer Realisierung der Klasse Fraction Berücksichtigung finden:

  • Objekte der Klasse Fraction sollen die von ihr verwaltete rationale Zahl immer in einer optimal gekürzten Form verwalteten. Schreiben Sie dazu eine Methode Reduce, die den Bruch optimal kürzt.

  • Rationale Zahlen können sowohl positiv als auch negativ sein. Um die klasseninterne Arbeitsweise der einzelnen Methoden zu vereinfachen, soll die Regel gelten, dass der Nenner einer rationalen Zahl immer positiv ist. Damit kann der Zähler eines Fraction-Objekts in Abhängigkeit vom Vorzeichen der rationalen Zahl entweder positiv oder negativ sein. Achten Sie darauf, dass bei allen Änderungen am Objektzustand (zum Beispiel nach einer Subtraktion) der Nenner positiv ist.

  • Gängige Konstruktoren zur Objekterzeugung.

  • Implementieren Sie die folgenden mathematischen Rechenoperationen:

    • Grundrechenoperationen (+, -, * und /).

    • Grundrechenoperationen in Verbindung mit dem Zuweisungsoperator (+=, -=, *= und /=).

    • Inkrement- und Dekrementoperator (++ und --).

    • Inverse einer rationalen Zahl (Operator ~).

    • Unärer Minus-Operator (-).

    • Vergleichsoperatoren (==, !=, <, <=, > und >=).

  • Methode Gcd (greatest common divisor) zur Bestimmung des größten gemeinsamen Teilers von Zähler und Nenner (ggT). Die Gcd-Methode kann von der Reduce-Methode verwendet werden.

  • Operatoren zur Typumwandlung. Ein Fraction-Objekt soll in einem arithmetischen Ausdruck auch dann verwendet werden können, wenn auf Grund des Kontextes ein float- oder double-Wert erwartet wird.

  • Ein- und Ausgabe. Eine rationale Zahl soll in der Form „Zähler / Nenner“ ausgegeben werden. Für Eingaben ist dasselbe Format zu Grunde zu legen. Zwischen den numerischen Werten und dem Schrägstrich sind beliebige Leerzeichen und Tabulatoren erlaubt.

2. Lösung

Schnittstelle der Klasse Fraction:

01: #include <iostream>
02: using namespace std;
03: 
04: class Fraction
05: {
06: private:
07:     // private member data
08:     int m_num;    // numerator
09:     int m_denom;  // denominator
10: 
11: public:
12:     // c'tors
13:     Fraction ();
14:     Fraction (int, int);
15: 
16:     // conversion c'tor
17:     Fraction (int);
18: 
19:     // getter / setter
20:     int GetNum () const { return m_num; };
21:     void SetNum (int num);
22:     int GetDenom () const { return m_denom; };
23:     void SetDenom (int denom);
24: 
25:     // unary arithmetic operators
26:     friend Fraction operator- (const Fraction&);
27:     friend Fraction operator~ (const Fraction&);
28: 
29:     // binary arithmetic operators
30:     friend Fraction operator+ (const Fraction&, const Fraction&);
31:     friend Fraction operator- (const Fraction&, const Fraction&);
32:     friend Fraction operator* (const Fraction&, const Fraction&);
33:     friend Fraction operator/ (const Fraction&, const Fraction&);
34: 
35:     // arithmetic-assignment operators
36:     friend const Fraction& operator+= (Fraction&, const Fraction&);
37:     friend const Fraction& operator-= (Fraction&, const Fraction&);
38:     friend const Fraction& operator*= (Fraction&, const Fraction&);
39:     friend const Fraction& operator/= (Fraction&, const Fraction&);
40: 
41:     // increment/decrement operators (prefix/postfix version)
42:     friend Fraction& operator++ (Fraction&);           // prefix increment
43:     friend const Fraction operator++ (Fraction&, int); // postfix increment
44:     friend Fraction& operator-- (Fraction&);           // prefix decrement
45:     friend const Fraction operator-- (Fraction&, int); // postfix decrement
46: 
47:     // comparison operators
48:     friend bool operator<= (const Fraction&, const Fraction&);
49:     friend bool operator<  (const Fraction&, const Fraction&);
50:     friend bool operator>= (const Fraction&, const Fraction&);
51:     friend bool operator>  (const Fraction&, const Fraction&);
52:     friend bool operator== (const Fraction&, const Fraction&);
53:     friend bool operator!= (const Fraction&, const Fraction&);
54: 
55:     // type conversion operator (Fraction -> double)
56:     operator double ();
57: 
58:     // input / output operators
59:     friend ostream& operator<< (ostream&, const Fraction&);
60:     friend istream& operator>> (istream&, Fraction&);
61: 
62: private:
63:     // private helper methods
64:     void CheckSigns ();
65:     void Reduce ();
66: 
67: private:
68:     // private class helper method
69:     static int Gcd (int n, int m);
70: };

Beispiel 1. Klasse Fraction: Schnittstelle.


Implementierung der Klasse Fraction:

001: #include "Fraction.h"
002: 
003: // c'tors
004: Fraction::Fraction () : m_num (0), m_denom (1)
005: {
006: }
007: 
008: Fraction::Fraction (int num, int denom)
009: {
010:     m_num = num;
011:     m_denom = (denom == 0) ? 1 : denom;
012:     CheckSigns ();
013:     Reduce ();
014: }
015: 
016: // conversion c'tor
017: Fraction::Fraction (int num)
018: {
019:     m_num = num;
020:     m_denom = 1;
021: }
022: 
023: // setter
024: void Fraction::SetNum (int num)
025: {
026:     m_num = num;
027:     Reduce ();
028: }
029: 
030: void Fraction::SetDenom (int denom)
031: {
032:     if (denom != 0)
033:     {
034:         m_denom = denom;
035:         CheckSigns ();
036:         Reduce ();
037:     }
038: }
039: 
040: // implementation of unary arithmetic operators
041: Fraction operator- (const Fraction& f)
042: {
043:     return Fraction (-f.m_num, f.m_denom);
044: }
045: 
046: Fraction operator~ (const Fraction& f)
047: {
048:     return Fraction (f.m_denom, f.m_num);
049: }
050: 
051: // implementation of binary arithmetic operators
052: Fraction operator+ (const Fraction& f1, const Fraction& f2)
053: {
054:     int num = f1.m_num * f2.m_denom + f1.m_denom * f2.m_num;
055:     int denom = f1.m_denom * f2.m_denom;
056:     return Fraction (num, denom);
057: }
058: 
059: Fraction operator- (const Fraction& f1, const Fraction& f2)
060: {
061:     int num = f1.m_num * f2.m_denom - f1.m_denom * f2.m_num;
062:     int denom = f1.m_denom * f2.m_denom;
063:     return Fraction (num, denom);
064: }
065: 
066: Fraction operator* (const Fraction& f1, const Fraction& f2)
067: {
068:     int num = f1.m_num * f2.m_num;
069:     int denom = f1.m_denom * f2.m_denom;
070:     return Fraction (num, denom);
071: }
072: 
073: Fraction operator/ (const Fraction& f1, const Fraction& f2)
074: {
075:     int num = f1.m_num * f2.m_denom;
076:     int denom = f1.m_denom * f2.m_num;
077:     return Fraction (num, denom);
078: }
079: 
080: // arithmetic-assignment operators
081: const Fraction& operator+= (Fraction& f1, const Fraction& f2)
082: {
083:     f1.m_num = f1.m_num * f2.m_denom + f1.m_denom * f2.m_num;
084:     f1.m_denom = f1.m_denom * f2.m_denom;
085:     f1.Reduce ();
086:     return f1;
087: }
088: 
089: const Fraction& operator-= (Fraction& f1, const Fraction& f2)
090: {
091:     f1.m_num = f1.m_num * f2.m_denom - f1.m_denom * f2.m_num;
092:     f1.m_denom = f1.m_denom * f2.m_denom;
093:     f1.Reduce ();
094:     return f1;
095: }
096: 
097: const Fraction& operator*= (Fraction& f1, const Fraction& f2)
098: {
099:     f1.m_num = f1.m_num * f2.m_num;
100:     f1.m_denom = f1.m_denom * f2.m_denom;
101:     f1.Reduce ();
102:     return f1;
103: }
104: 
105: const Fraction& operator/= (Fraction& f1, const Fraction& f2)
106: {
107:     f1.m_num = f1.m_num * f2.m_denom;
108:     f1.m_denom = f1.m_denom * f2.m_num;
109:     f1.Reduce ();
110:     return f1;
111: }
112: 
113: // comparison operators   
114: bool operator<= (const Fraction& f1, const Fraction& f2)
115: {
116:     return f1.m_num * f2.m_denom <= f1.m_denom * f2.m_num;
117: }
118: 
119: bool operator< (const Fraction& f1, const Fraction& f2)
120: {
121:     return f1.m_num * f2.m_denom < f1.m_denom * f2.m_num;
122: }
123: 
124: bool operator>= (const Fraction& f1, const Fraction& f2)
125: {
126:     return ! (f1 < f2);
127: }
128: 
129: bool operator> (const Fraction& f1, const Fraction& f2)
130: {
131:     return ! (f1 <= f2);
132: }
133: 
134: bool operator== (const Fraction& f1, const Fraction& f2)
135: {
136:     return f1.m_num * f2.m_denom == f1.m_denom * f2.m_num;
137: }
138: 
139: bool operator!= (const Fraction& f1, const Fraction& f2)
140: {
141:     return ! (f1 == f2);
142: }
143: 
144: // conversion operator (Fraction -> double)
145: Fraction::operator double ()
146: {
147:     return (double) m_num / (double) m_denom;
148: }
149: 
150: // increment operator: prefix version
151: Fraction& operator++ (Fraction& f)
152: {
153:     f += 1;
154:     return f;
155: }
156: 
157: // decrement operator: prefix version
158: Fraction& operator-- (Fraction& f)
159: {
160:     f -= 1;
161:     return f;
162: }
163: 
164: // increment operator: postfix version
165: const Fraction operator++ (Fraction& f, int)
166: {
167:     Fraction tmp (f); // construct a copy
168:     ++ f;             // increment number
169:     return tmp;       // return the copy
170: }
171: 
172: // decrement operator: postfix version
173: const Fraction operator-- (Fraction& f, int)
174: {
175:     Fraction tmp (f); // construct a copy
176:     -- f;             // decrement number
177:     return tmp;       // return the copy
178: }
179: 
180: // private helper method
181: void Fraction::CheckSigns ()
182: {
183:     // normalize fraction
184:     if (m_denom < 0)
185:     {
186:         m_num *= -1;
187:         m_denom *= -1;
188:     }
189: }
190: 
191: void Fraction::Reduce ()
192: {
193:     int sign = (m_num < 0) ? -1 : 1;
194:     int gcd = Fraction::Gcd (sign * m_num, m_denom);
195:     m_num /= gcd;
196:     m_denom /= gcd;
197: }
198: 
199: int Fraction::Gcd (int n, int m)
200: {
201:     if (n != m)
202:     {
203:         // calculate greatest common divisor of numerator and denominator
204:         while (m > 0)
205:         {
206:             int rem = n % m;
207:             n = m;
208:             m = rem;
209:         }
210:     }
211: 
212:     return n;
213: }
214: 
215: // output operator
216: ostream& operator<< (ostream& os, const Fraction& f)
217: {
218:     os << f.m_num << '/' << f.m_denom;
219:     return os;
220: }
221: 
222: // input operator
223: istream& operator>> (istream& is, Fraction& f)
224: {
225:     is >> f.m_num >> f.m_denom;
226:     f.Reduce();
227:     return is;
228: }

Beispiel 2. Klasse Fraction: Implementierung.


In gewohnter Manier schließen wir die Realisierung mit einem möglichst umfassenden Testrahmen ab:

#include "Fraction.h"

void DemoUnaryOperators()
{
    Fraction f (1, 2);
    cout << "f  = " << f << endl;
    f = -f;
    cout << "-f = " << f << endl;
    f = ~f;
    cout << "~f = " << f << endl;
}

void DemoBinaryOperators()
{
    Fraction a (1, 7);
    Fraction b (3, 7);
    Fraction c;

    cout << "a  = " << a << endl;
    cout << "b  = " << b << endl;

    c = a + b;
    cout << "a + b = " << c << endl;

    c = a + a + a;
    cout << "a + a + a = " << c << endl;

    c = a - b - a;
    cout << "a - b - a = " << c << endl;

    c = a * b;
    cout << "a * b = " << c << endl;

    c = a / b;
    cout << "a / b = " << c << endl;
}

void DemoArithmeticAssigmentOperators()
{
    Fraction a (1, 7);
    Fraction b (3, 7);
    Fraction c (5, 7);

    cout << "a  = " << a << endl;
    cout << "b  = " << b << endl;
    cout << "c  = " << c << endl;

    a += b += c;
    cout << "a += b += c:" << endl;
    cout << a << endl;
    cout << b << endl;
    cout << c << endl;

    c -= b -= a;
    cout << "c -= b -= a:" << endl;
    cout << a << endl;
    cout << b << endl;
    cout << c << endl;
}

void DemoComparisonOperators()
{
    Fraction f (1, 2);
    Fraction g (1, 3);

    cout << "f  = " << f << endl;
    cout << "g  = " << g << endl;

    cout << "f < g:  " << (f < g)  << endl;
    cout << "f <= g: " << (f <= g) << endl;
    cout << "f > g:  " << (f > g)  << endl;
    cout << "f >= g: " << (f >= g) << endl;
    cout << "f == g: " << (f == g) << endl;
    cout << "f != g: " << (f != g) << endl;
}

void DemoTypeConversionOperators()
{
    Fraction a;

    a = 1;
    cout << a << endl;

    a = a + (Fraction) 1;
    a = 1 + (int) (double) a;
    cout << a << endl;
}

void DemoTypeConversionOperator02()
{
    Fraction f (1, 7);
    double d = f;
    cout << "d: " << d << endl;
}

void DemoIncrementOperators()
{
    Fraction f (1, 2);
    Fraction g;

    cout << "f: " << f << endl;

    g = f ++;
    cout << "g: " << g << endl;

    g = ++ f;
    cout << "g: " << g << endl;

    g = f --;
    cout << "g: " << g << endl;

    g = -- f;
    cout << "g: " << g << endl;
}

void DemoInputOutput()
{
    Fraction f (1, 2);
    cout << "f: " << f << endl;

    Fraction g;
    cout << "enter num and denom:" << endl;

    cin >> g;
    cout << "g: " << g << endl;
}

void main ()
{
    DemoUnaryOperators();
    DemoBinaryOperators();
    DemoArithmeticAssigmentOperators();
    DemoComparisonOperators();
    DemoTypeConversionOperators();
    DemoTypeConversionOperator02();
    DemoIncrementOperators();
    DemoInputOutput ();
}

Ausgabe:

f  = 1/2
-f = -1/2
~f = -2/1
a  = 1/7
b  = 3/7
a + b = 4/7
a + a + a = 3/7
a - b - a = -3/7
a * b = 3/49
a / b = 1/3
a  = 1/7
b  = 3/7
c  = 5/7
a += b += c:
9/7
8/7
5/7
c -= b -= a:
9/7
-1/7
6/7
f  = 1/2
g  = 1/3
f < g:  0
f <= g: 0
f > g:  1
f >= g: 1
f == g: 0
f != g: 1
1/1
3/1
d: 0.142857
f: 1/2
g: 1/2
g: 5/2
g: 5/2
g: 1/2
f: 1/2
enter num and denom:
2 8
g: 1/4