001package hw4;
002
003/** <b>RatNum</b> represents an <b>immutable</b> rational number.
004    It includes all of the elements in the set of rationals, as well
005    as the special "NaN" (not-a-number) element that results from
006    division by zero.
007    <p>
008    The "NaN" element is special in many ways.  Any arithmetic
009    operation (such as addition) involving "NaN" will return "NaN".
010    With respect to comparison operations, such as less-than, "NaN" is
011    considered equal to itself, and larger than all other rationals.
012    <p>
013    Examples of RatNums include "-1/13", "53/7", "4", "NaN", and "0".
014 */
015
016// ("immutable" is a common term for which "Effective Java" (p. 63)
017// provides the following definition: "An immutatable class is simply
018// a class whose instances cannot be modified.  All of the information
019// contained in each instance is provided when it is created and is
020// fixed for the lifetime of the object.")
021public final class RatNum extends Number implements Comparable<RatNum> {
022
023  private final int numer;
024  private final int denom;
025
026  // Abstraction Function:
027  //   A RatNum r is NaN if r.denom = 0, (r.numer / r.denom) otherwise.
028  // (An abstraction function explains what the state of the fields in a
029  // RatNum represents.  In this case, a rational number can be
030  // understood as the result of dividing two integers, or not-a-number
031  // if we would be dividing by zero.)
032
033  // Representation invariant for every RatNum r:
034  //   (r.denom >= 0) &&
035  //   (r.denom > 0 ==> there does not exist integer i > 1 such that
036  //                    r.numer mod i = 0 and r.denom mod i = 0;)
037  //   In other words,
038  //     * r.denom is always non-negative.
039  //     * r.numer/r.denom is in reduced form (assuming r.denom is not zero).
040  // (A representation invariant tells us something that is true for all
041  // instances of a RatNum)
042
043  /** A constant holding a Not-a-Number (NaN) value of type RatNum */
044  public static final RatNum NaN = new RatNum(1, 0);
045
046  /** A constant holding a zero value of type RatNum */
047  public static final RatNum ZERO = new RatNum(0);
048
049  /** @param n The value of the new RatNum.
050        @effects Constructs a new RatNum = n.
051   */
052  public RatNum(int n) {
053    numer = n;
054    denom = 1;
055    checkRep();
056  }
057
058  /** @param n The numerator of the new RatNum.
059        @param d The denominator of the new RatNum.
060        @effects If d = 0, constructs a new RatNum = NaN.  Else
061         constructs a new RatNum = (n / d).
062   */
063  public RatNum(int n, int d) {
064    // special case for zero denominator; gcd(n,d) requires d != 0
065    if (d == 0) {
066      numer = n;
067      denom = 0;
068
069    } else {
070
071      // reduce ratio to lowest terms
072      int g = gcd(n,d);
073      n = n / g;
074      d = d / g;
075
076      if (d < 0) {
077        numer = -n;
078        denom = -d;
079      } else {
080        numer = n;
081        denom = d;
082      }
083    }
084    checkRep();
085  }
086
087  /**
088   * Checks that the representation invariant holds (if any).
089   **/
090  // Throws a RuntimeException if the rep invariant is violated.
091  private void checkRep() throws RuntimeException {
092    if (denom < 0) {
093      throw new RuntimeException("Denominator of a RatNum cannot be less than zero");
094    }
095
096    if (denom > 0) {
097      int thisGcd = gcd (numer, denom);
098      if ((thisGcd != 1) && (thisGcd != -1)) {
099        throw new RuntimeException("RatNum not in lowest form");
100      }
101    }
102  }
103
104  /** Returns true if this is NaN
105        @return true iff this is NaN (not-a-number)
106   */
107  public boolean isNaN() {
108    return (denom == 0);
109  }
110
111  /** Returns true if this is negative.
112        @return true iff this < 0. */
113  public boolean isNegative() {
114    return (compareTo(ZERO) < 0);
115  }
116
117  /** Returns true if this is positive.
118        @return true iff this > 0. */
119  public boolean isPositive() {
120    return (compareTo(ZERO) > 0);
121  }
122
123  /** Compares two RatNums.
124        @param rn The RatNum to be compared.
125        @requires rn != null
126        @return a negative number if this < rn,
127        0 if this = rn,
128        a positive number if this > rn.
129   */
130  @Override
131  public int compareTo(RatNum rn) {
132    if (this.isNaN() && rn.isNaN()) {
133      return 0;
134    } else if (this.isNaN()) {
135      return 1;
136    } else if (rn.isNaN()) {
137      return -1;
138    } else {
139      RatNum diff = this.sub(rn);
140      return diff.numer;
141    }
142  }
143
144  /** Approximates the value of this rational.
145        @return a double approximation for this.  Note that "NaN" is
146        mapped to {@link Double#NaN}, and the {@link Double#NaN} value
147        is treated in a special manner by several arithmetic operations,
148        such as the comparison and equality operators.  See the
149        <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.2.3">
150        Java Language Specification, section 4.2.3</a>, for more details.
151   */
152  @Override
153  public double doubleValue() {
154    if (isNaN()) {
155      return Double.NaN;
156    } else {
157      // convert int values to doubles before dividing.
158      return ((double)numer) / ((double)denom);
159    }
160  }
161
162  /** Returns an integer approximation for this.  The rational number
163        is rounded to the nearest integer.
164   */
165  @Override
166  public int intValue() {
167    // round to nearest by adding +/- .5 before truncating division.
168    // we expect the implementation to use "round half away from zero".
169    // for more info, see http://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero
170
171    // note that even though the result is guaranteed to fit in an
172    // int, we need to use longs for the computation.
173    if (numer >= 0) {
174      return (int) (((long)numer + (denom/2)) / denom);
175    } else {
176      return (int) (((long)numer - (denom/2)) / denom);
177    }
178  }
179
180  /** Returns a float approximation for this.  This method is
181        specified by our superclass, Number.
182   */
183  @Override
184  public float floatValue() {
185    return (float) doubleValue();
186  }
187
188
189  /** Returns a long approximation for this.  This method is
190        specified by our superclass, Number.  The value returned
191        is rounded to the nearest long.
192   */
193  @Override
194  public long longValue() {
195    return intValue();
196  }
197
198
199  // in the implementation comments for the following methods, <this>
200  // is notated as "a/b" and <arg> likewise as "x/y"
201
202  /** Returns the additive inverse of this RatNum.
203        @return a Rational equal to (0 - this).
204   */
205  public RatNum negate() {
206    return new RatNum(- this.numer, this.denom);
207  }
208
209  /** Addition operation.
210        @param arg The other value to be added.
211        @requires arg != null
212        @return a RatNum equal to (this + arg).
213        If either argument is NaN, then returns NaN.
214   */
215  public RatNum add(RatNum arg) {
216    // a/b + x/y = ay/by + bx/by = (ay + bx)/by
217    return new RatNum((this.numer*arg.denom) + (arg.numer*this.denom),
218        this.denom*arg.denom);
219  }
220
221  /** Subtraction operation.
222        @param arg The value to be subtracted.
223        @requires arg != null
224        @return a RatNum equal to (this - arg).
225        If either argument is NaN, then returns NaN.
226   */
227  public RatNum sub(RatNum arg) {
228    // a/b - x/y = a/b + -x/y
229    return this.add(arg.negate());
230  }
231
232  /** Multiplication operation.
233        @param arg The other value to be multiplied.
234        @requires arg != null
235        @return a RatNum equal to (this * arg).
236        If either argument is NaN, then returns NaN.
237   */
238  public RatNum mul(RatNum arg) {
239    // (a/b) * (x/y) = ax/by
240    return new RatNum(this.numer*arg.numer,
241        this.denom*arg.denom );
242  }
243
244  /** Division operation.
245        @param arg The divisor.
246        @requires arg != null
247        @return a RatNum equal to (this / arg).
248        If arg is zero, or if either argument is NaN, then returns NaN.
249   */
250  public RatNum div(RatNum arg) {
251    // (a/b) / (x/y) = ay/bx
252    if (arg.isNaN()) {
253      return arg;
254    } else {
255      return new RatNum(this.numer*arg.denom,
256          this.denom*arg.numer);
257    }
258  }
259
260  /** Returns the greatest common divisor of 'a' and 'b'.
261        @param a, b The numbers for which to find the GCD.
262        @requires b != 0
263        @return d such that a % d = 0 and b % d = 0
264   */
265  private static int gcd(int a, int b) {
266    // Euclid's method
267    if (b == 0) {
268      return 0;
269    }
270    while (b != 0) {
271      int tmp = b;
272      b = a % b;
273      a = tmp;
274    }
275    return a;
276  }
277
278  /** Standard hashCode function.
279        @return an int that all objects equal to this will also
280        return.
281   */
282  @Override
283  public int hashCode() {
284    // all instances that are NaN must return the same hashcode;
285    if (this.isNaN()) {
286      return 0;
287    }
288    return (this.numer*2) + (this.denom*3);
289  }
290
291  /** Standard equality operation.
292        @param obj The object to be compared for equality.
293        @return true if and only if 'obj' is an instance of a RatNum
294        and 'this' and 'obj' represent the same rational number.  Note
295        that NaN = NaN for RatNums.
296   */
297  @Override
298  public boolean equals(/*@Nullable*/ Object obj) {
299    if (obj instanceof RatNum) {
300      RatNum rn = (RatNum) obj;
301
302      // special case: check if both are NaN
303      if (this.isNaN() && rn.isNaN()) {
304        return true;
305      } else {
306        return (this.numer == rn.numer) && (this.denom == rn.denom);
307      }
308    } else {
309      return false;
310    }
311  }
312
313  /** @return a String representing this, in reduced terms.
314        The returned string will either be "NaN", or it will take on
315        either of the forms "N" or "N/M", where N and M are both
316        integers in decimal notation and M != 0.
317   */
318  @Override
319  public String toString() {
320    // using '+' as String concatenation operator in this method
321    if (isNaN()) {
322      return "NaN";
323    } else if (denom != 1) {
324      return numer + "/" + denom;
325    } else {
326      return Integer.toString(numer);
327    }
328  }
329
330  /** Makes a RatNum from a string describing it.
331        @param ratStr A string of the format described in the @requires clause.
332        @requires 'ratStr' is an instance of a string, with no spaces,
333        of the form: <UL>
334        <LI> "NaN"
335        <LI> "N/M", where N and M are both integers in
336        decimal notation, and M != 0, or
337        <LI> "N", where N is an integer in decimal
338        notation.
339        </UL>
340        @returns NaN if ratStr = "NaN".  Else returns a
341        RatNum r = ( N / M ), letting M be 1 in the case
342        where only "N" is passed in.
343   */
344  public static RatNum valueOf(String ratStr) {
345    int slashLoc = ratStr.indexOf('/');
346    if (ratStr.equals("NaN")) {
347      return new RatNum(1,0);
348    } else if (slashLoc == -1) {
349      // not NaN, and no slash, must be an Integer
350      return new RatNum( Integer.parseInt( ratStr ) );
351    } else {
352      // slash, need to parse the two parts separately
353      int n = Integer.parseInt(ratStr.substring(0, slashLoc));
354      int d = Integer.parseInt(ratStr.substring(slashLoc+1,
355          ratStr.length()));
356      return new RatNum(n, d);
357    }
358  }
359
360  /**
361   * Declare a serialization version number. This field is necessary because
362   * our parent class (Number) implements Serializable; see the api docs for
363   * java.lang.Serializable for more details.
364   */
365  private static final long serialVersionUID = -8593953691277016262L;
366}