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:
  1. 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.
  2. It is reusable: any future program that needs to do anything with fractions can use this program, just substituting a different main.
  3. It is self-checking: after every operation, validity is checked; invalid results propagate through all operations.
  4. 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.