5.15.3. An Example of a Class

Real numbers of type double have a serious problem: they are not always exactly correct due to roundoff error. Often, that is of little consequence. But sometimes it can lead to results that are way off.

One approach to dealing with that is to create a new type of rational numbers, where a rational number is quotient of two integers. For example, 1/3 can be represented exactly, as a rational number with a numerator of 1 and a denominator of 3.

The following class implements rational numbers with addition, subtraction, multiplication and division.

class Rational
{
  private long numerator;
  private long denominator;

  //-----------------------------------------
  //            constructor
  //-----------------------------------------

  public Rational(long num, long den)
  {
    numerator   = num;
    denominator = den;
  }

  //-----------------------------------------
  //              accessors
  //-----------------------------------------

  public long getNumerator()
  {
    return numerator;
  }

  public long getDenominator()
  {
    return denominator;
  }

  //-----------------------------------------
  //                gcd
  //-----------------------------------------
  // gcd(a,b) returns the greatest common
  // denominator of a and b.
  //-----------------------------------------

  private long gcd(long a, long b)
  {
    if(a == 0) 
    {
      return b;
    }
    else {
      return gcd(b % a, a);
    }
  }

  //-----------------------------------------
  //               plus
  //-----------------------------------------
  // x.plus(y) yields the sum of x and y.
  //-----------------------------------------

  public Rational plus(Rational y)
  {
    //-----------------------------------------
    // Note: denominator is this.denominator --
    // the denominator of this object, x in the comment
    // above.  Note that we can get y.denominator
    // because y has type Rational.  To simplify,
    // we express the problem as adding (a/b) + (c/d).
    //-----------------------------------------

    long a      = numerator;
    long b      = denominator;
    long c      = y.numerator;
    long d      = y.denominator;

    long newnum = a*d + c*b;
    long newden = b*d;
    long g      = gcd(newnum, newden);

    return new Rational(newnum/g, newden/g);
  }

  //-----------------------------------------
  //              minus
  //-----------------------------------------
  // x.minus(y) yields the difference of x - y.
  //-----------------------------------------

  public Rational minus(Rational y)
  {
     return plus(new Rational(-y.numerator, y.denominator));
  }

  //-----------------------------------------
  //               times
  //-----------------------------------------
  // x.times(y) yields the product of x and y.
  //-----------------------------------------

  public Rational times(Rational y)
  {
    //-----------------------------------------
    // The goal is to compute (a/b)*(c/d).
    //-----------------------------------------

    long a      = numerator;
    long b      = denominator;
    long c      = y.numerator;
    long d      = y.denominator;

    long newnum = a*c;
    long newden = b*d;
    long g      = gcd(newnum, newden);

    return new Rational(newnum/g, newden/g);
  }

  //-----------------------------------------
  //              divideBy
  //-----------------------------------------
  // x.divideBy(y) yields x/y.
  //-----------------------------------------
  public Rational dividedBy(Rational y)
  {
    return times(new Rational(y.denominator, y.numerator));
  }
}

Now, to use rational numbers, you might do the following.

  Rational twentyfive = new Rational(25, 1);
  Rational thirtyfive = new Rational(35, 1);
  Rational x          = twentyfive.divideBy(thirtyfive);

  System.out.printf("25/35 = %d/%d",
                    x.getNumerator(),
                    x.getDenominator());  

Note that here we must use x.getNumerator() and x.getDenominator() instead of x.numerator and x.denominator since we are not in class Rational.


Private variables

Class Rational offers a good example of why variables are typically private. We rely on rational numbers being reduced; the numerator and denominator have no common factors.

But if instance variables numerator and denominator are public, then another class could set them to arbitrary values, and that assumption would not necessarily hold.

Another issue is stability of rational numbers. With private variables, we can advertise that, once an object of class Rational has been created, it will never change. With public variables, we cannot advertise that.


Warning about rational numbers

Rational numbers are appealing, but they have a serious drawback. As you perform computations, the numerator and denominator have a tendency to grow very large. The class above does not take that into account.

Another issue is division by 0. The class above just assumes that it will never happen. An attempt to divide by 0 will throw DivideByZeroException.