EEN218 Class Notes Thursday 2004-02-05
Version 1
Here is a complete version of the original program. Its job is simply to compute
a+b-c*d+(1/2) where a, b, c, and d are fractions provided by the user.
#include <iostream>
void main()
{ int at, ab, bt, bb, ct, cb, dt, db, zt, zb;
cout << "This program computes a+b-c*d+(1/2), where a, b, c, d are fractions\n";
cout << "enter fractions as two integers with a / between them, e.g. 3/4\n";
char div;
cout << "Enter a: ";
cin >> at >> div >> ab;
cout << "Enter b: ";
cin >> bt >> div >> bb;
cout << "Enter c: ";
cin >> ct >> div >> cb;
cout << "Enter d: ";
cin >> dt >> div >> db;
zt=2*(at*bb+ab*bt)*cb*db+ab*bb*(cb*db-2*ct*dt);
zb=2*ab*bb*cb*db;
cout << "(" << at << "/" << ab << ")+("
<< bt << "/" << bb << ")-("
<< ct << "/" << cb << ")*("
<< dt << "/" << db << ")+(1/2) = "
<< "(" << zt << "/" << zb << ")\n"; }
It is a very simple program, that sort of does the job. ("sort of" because it
doesn't reduce the result to its simplest terms, and it doesn't check for
errors or invalid computations). It is not maintainable: the complicated
computation for zt is correct (I think), but every time the results
are in doubt, somebody will have to work it out all over again; there is
no other way to verify it. This program is also a dead end: it does not
provide any useful components that could be reused in similar programs.
It also could not very well be used or created by a non-programmer.
Version 2
If we make things a little more abstract, things improve. A fraction is
represented by three variables: its top and bottom (ints) and whether
or not it is valid (bool). We create functions that perform all the
common operations on fractions using this representation.
#include <iostream>
#include <string>
void read_fraction(string prompt, int & top, int & bot, bool & ok)
{ cout << prompt;
if (cin.fail()) cin.clear();
char div;
cin >> top >> div >> bot;
ok = (div=='/') && (bot!=0) && !cin.fail(); }
void print_fraction(int top, int bot, bool ok)
{ if (ok)
cout << '(' << top << '/' << bot << ')';
else
cout << "invalid"; }
int gcd(int a, int b)
{ while (b!=0)
{ int t=a%b; a=b; b=t; }
return a; }
void add_fraction(int atop, int abot, bool aok, int btop, int bbot, bool bok, int & rtop, int & rbot, bool & rok)
{ rtop=atop*bbot+btop*abot;
rbot=abot*bbot;
rok=aok && bok && rbot!=0;
int g=gcd(rtop, rbot);
if (g!=0)
{ rtop/=g;
rbot/=g; } }
void subtract_fraction(int atop, int abot, bool aok, int btop, int bbot, bool bok, int & rtop, int & rbot, bool & rok)
{ rtop=atop*bbot-btop*abot;
rbot=abot*bbot;
rok=aok && bok && rbot!=0;
int g=gcd(rtop, rbot);
if (g!=0)
{ rtop/=g;
rbot/=g; } }
void multiply_fraction(int atop, int abot, bool aok, int btop, int bbot, bool bok, int & rtop, int & rbot, bool & rok)
{ rtop=atop*btop;
rbot=abot*bbot;
rok=aok && bok && rbot!=0;
int g=gcd(rtop, rbot);
if (g!=0)
{ rtop/=g;
rbot/=g; } }
void divide_fraction(int atop, int abot, bool aok, int btop, int bbot, bool bok, int & rtop, int & rbot, bool & rok)
{ rtop=atop*bbot;
rbot=abot*btop;
rok=aok && bok && rbot!=0;
int g=gcd(rtop, rbot);
if (g!=0)
{ rtop/=g;
rbot/=g; } }
void assign_fraction(int atop, int abot, bool aok, int & rtop, int & rbot, bool & rok)
{ rtop=atop;
rbot=abot;
rok=aok; }
void assign_fraction(int atop, int abot, int & rtop, int & rbot, bool & rok)
{ rtop=atop;
rbot=abot;
rok=abot!=0; }
void main()
{ int at, ab, bt, bb, ct, cb, dt, db, halft, halfb, xt, xb, yt, yb, zt, zb;
bool aok, bok, cok, dok, halfok, xok, yok, zok;
cout << "This program computes a+b-c*d+(1/2), where a, b, c, d are fractions\n";
cout << "enter fractions as two integers with a / between them, e.g. 3/4\n";
read_fraction("Enter a: ", at, ab, aok);
read_fraction("Enter b: ", bt, bb, bok);
read_fraction("Enter c: ", ct, cb, cok);
read_fraction("Enter d: ", dt, db, dok);
assign_fraction(1, 2, halft, halfb, halfok);
add_fraction(at, ab, aok, bt, bb, bok, xt, xb, xok); // x=a+b
multiply_fraction(ct, cb, cok, dt, db, dok, yt, yb, yok); // y=c*d
subtract_fraction(xt, xb, xok, yt, yb, yok, zt, zb, zok); // z=x-y = (a+b)-(c*d)
add_fraction(zt, zb, zok, halft, halfb, halfok, zt, zb, zok); // z=z+(1/2)
print_fraction(at, bt, aok);
cout << "+";
print_fraction(bt, bb, bok);
cout << "-";
print_fraction(ct, cb, cok);
cout << "*";
print_fraction(dt, db, dok);
cout << "+(1/2) = ";
print_fraction(zt, zb, zok);
cout << "\n"; }
The program is undeniably much bigger and more complicated, but there are four enormous
advantages:
- It is maintainable: there are no complicated operations. The four arithmetic
functions just implement the laws of algebra, and can be verified by any competent
person. The main program just uses those functions to perform the necessary operations.
Once you are convinced that the simple functions are correct, there is no way
main could not also be correct.
- It is reusable: any future program that needs to do anything with fractions
can use this program, just substituting a different main.
- It is self-checking: after every operation, validity is checked; invalid results
propagate through all operations.
- It is easier to use. A complete non-expert could (more-or-less) create a
valid main using these functions to perform any computation; he/she/it could
not forget about the validity checks because they are built in. All results
are autmatically reduced to their simplest terms (6/8 -> 3/4), so that
can't be forgotten either.
Version 3
But still, it is not nice to use. Three variables and three parameters for
each basic value. The next step is to give fractions almost the same status
in a program as ints and floats. We define a struct to represent
a fraction; then all functions can have fraction parameters, and main can have
fraction variables.
#include <iostream>
#include <string>
struct fraction
{ int top, bot;
bool ok; };
fraction read_fraction(string prompt)
{ cout << prompt;
fraction r;
if (cin.fail()) cin.clear();
char div;
cin >> r.top >> div >> r.bot;
r.ok = (div=='/') && (r.bot!=0) && !cin.fail();
return r; }
void print(fraction a)
{ if (a.ok)
cout << '(' << a.top << '/' << a.bot << ')';
else
cout << "invalid"; }
int gcd(int a, int b)
{ while (b!=0)
{ int t=a%b; a=b; b=t; }
return a; }
fraction add(fraction a, fraction b)
{ fraction r;
r.top=a.top*b.bot+b.top*a.bot;
r.bot=a.bot*b.bot;
r.ok=a.ok && b.ok && r.bot!=0;
int g=gcd(r.top, r.bot);
if (g!=0)
{ r.top/=g;
r.bot/=g; }
return r; }
fraction subtract(fraction a, fraction b)
{ fraction r;
r.top=a.top*b.bot-b.top*a.bot;
r.bot=a.bot*b.bot;
r.ok=a.ok && b.ok && r.bot!=0;
int g=gcd(r.top, r.bot);
if (g!=0)
{ r.top/=g;
r.bot/=g; }
return r; }
fraction multiply(fraction a, fraction b)
{ fraction r;
r.top=a.top*b.top;
r.bot=a.bot*b.bot;
r.ok=a.ok && b.ok && r.bot!=0;
int g=gcd(r.top, r.bot);
if (g!=0)
{ r.top/=g;
r.bot/=g; }
return r; }
fraction divide(fraction a, fraction b)
{ fraction r;
r.top=a.top*b.bot;
r.bot=a.bot*b.top;
r.ok=a.ok && b.ok && r.bot!=0;
int g=gcd(r.top, r.bot);
if (g!=0)
{ r.top/=g;
r.bot/=g; }
return r; }
fraction make_fraction(int t, int b)
{ fraction r;
r.top=t;
r.bot=b;
r.ok=(b!=0);
return r; }
void main()
{ cout << "This program computes a+b-c*d+(1/2), where a, b, c, d are fractions\n";
cout << "enter fractions as two integers with a / between them, e.g. 3/4\n";
fraction a=read_fraction("Enter a: ");
fraction b=read_fraction("Enter b: ");
fraction c=read_fraction("Enter c: ");
fraction d=read_fraction("Enter d: ");
fraction z=subtract(add(a, b), multiply(c, d));
z=add(z, make_fraction(1,2));
print(a);
cout << "+";
print(b);
cout << "-";
print(c);
cout << "*";
print(d);
cout << "+(1/2) = ";
print(z);
cout << "\n"; }
Everything is so much simpler. Main is just-about fool-proof, it certainly doesn't
take a programmer to use these new functions,and it is all nicely readble too.
It would be nice if we could replace
z=subtract(add(a, b), multiply(c, d));
with
z=a+b-c*d;
and one day we will, but that can wait for a while.