001package hw3;
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 integer 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 int,
172        // we need to use longs for the computation because the
173        // intermediate results might not fit in an int.
174        if (numer >= 0) {
175            return (int) (((long)numer + (denom/2)) / denom);
176        } else {
177            return (int) (((long)numer - (denom/2)) / denom);
178        }
179    }
180
181    /** Returns a float approximation for this.  This method is
182        specified by our superclass, Number.
183     */
184    @Override
185    public float floatValue() {
186        return (float) doubleValue();
187    }
188
189
190    /** Returns a long approximation for this.  This method is
191        specified by our superclass, Number.  The value returned
192        is rounded to the nearest long.
193     */
194    @Override
195    public long longValue() {
196        // The approximation is guaranteed to within the representable
197        // range of int values (because the numerator and denominators are
198        // represented as int, not long), so returning intValue is correct.
199        return intValue();
200    }
201
202
203    // in the implementation comments for the following methods, <this>
204    // is notated as "a/b" and <arg> likewise as "x/y"
205
206    /** Returns the additive inverse of this RatNum.
207        @return a Rational equal to (0 - this).
208    */
209    public RatNum negate() {
210        return new RatNum(- this.numer, this.denom);
211    }
212
213    /** Addition operation.
214        @param arg The other value to be added.
215        @requires arg != null
216        @return a RatNum equal to (this + arg).
217        If either argument is NaN, then returns NaN.
218    */
219    public RatNum add(RatNum arg) {
220        // a/b + x/y = ay/by + bx/by = (ay + bx)/by
221        return new RatNum(this.numer*arg.denom + arg.numer*this.denom,
222                          this.denom*arg.denom);
223    }
224
225    /** Subtraction operation.
226        @param arg The value to be subtracted.
227        @requires arg != null
228        @return a RatNum equal to (this - arg).
229        If either argument is NaN, then returns NaN.
230    */
231    public RatNum sub(RatNum arg) {
232        // a/b - x/y = a/b + -x/y
233        return this.add(arg.negate());
234    }
235
236    /** Multiplication operation.
237        @param arg The other value to be multiplied.
238        @requires arg != null
239        @return a RatNum equal to (this * arg).
240        If either argument is NaN, then returns NaN.
241    */
242    public RatNum mul(RatNum arg) {
243        // (a/b) * (x/y) = ax/by
244        return new RatNum(this.numer*arg.numer,
245                          this.denom*arg.denom );
246    }
247
248    /** Division operation.
249        @param arg The divisor.
250        @requires arg != null
251        @return a RatNum equal to (this / arg).
252        If arg is zero, or if either argument is NaN, then returns NaN.
253    */
254    public RatNum div(RatNum arg) {
255        // (a/b) / (x/y) = ay/bx
256        if (arg.isNaN()) {
257            return arg;
258        } else {
259            return new RatNum(this.numer*arg.denom,
260                              this.denom*arg.numer);
261        }
262    }
263
264    /** Returns the greatest common divisor of 'a' and 'b'.
265        @param a, b The numbers for which to find the GCD.
266        @requires b != 0
267        @return d such that a % d = 0 and b % d = 0
268    */
269    private static int gcd(int a, int b) {
270        // Euclid's method
271        if (b == 0) {
272            return 0;
273        }
274        while (b != 0) {
275            int tmp = b;
276            b = a % b;
277            a = tmp;
278        }
279        return a;
280    }
281
282    /** Standard hashCode function.
283        @return an int that all objects equal to this will also
284        return.
285    */
286    @Override
287    public int hashCode() {
288        // all instances that are NaN must return the same hashcode;
289        if (this.isNaN()) {
290            return 0;
291        }
292        return this.numer*2 + this.denom*3;
293    }
294
295    /** Standard equality operation.
296        @param obj The object to be compared for equality.
297        @return true if and only if 'obj' is an instance of a RatNum
298        and 'this' and 'obj' represent the same rational number.  Note
299        that NaN = NaN for RatNums.
300    */
301    @Override
302    public boolean equals(/*@Nullable*/ Object obj) {
303        if (obj instanceof RatNum) {
304            RatNum rn = (RatNum) obj;
305
306            // special case: check if both are NaN
307            if (this.isNaN() && rn.isNaN()) {
308                return true;
309            } else {
310                return this.numer == rn.numer && this.denom == rn.denom;
311            }
312        } else {
313            return false;
314        }
315    }
316
317    /** @return a String representing this, in reduced terms.
318        The returned string will either be "NaN", or it will take on
319        either of the forms "N" or "N/M", where N and M are both
320        integers in decimal notation and M != 0.
321    */
322    @Override
323    public String toString() {
324        // using '+' as String concatenation operator in this method
325        if (isNaN()) {
326            return "NaN";
327        } else if (denom != 1) {
328            return numer + "/" + denom;
329        } else {
330            return Integer.toString(numer);
331        }
332    }
333
334    /** Makes a RatNum from a string describing it.
335        @param ratStr A string of the format described in the @requires clause.
336        @requires 'ratStr' is an instance of a string, with no spaces,
337        of the form: <UL>
338        <LI> "NaN"
339        <LI> "N/M", where N and M are both integers in
340        decimal notation, and M != 0, or
341        <LI> "N", where N is an integer in decimal
342        notation.
343        </UL>
344        @returns NaN if ratStr = "NaN".  Else returns a
345        RatNum r = ( N / M ), letting M be 1 in the case
346        where only "N" is passed in.
347    */
348    public static RatNum valueOf(String ratStr) {
349        int slashLoc = ratStr.indexOf('/');
350        if (ratStr.equals("NaN")) {
351            return new RatNum(1,0);
352        } else if (slashLoc == -1) {
353            // not NaN, and no slash, must be an Integer
354            return new RatNum( Integer.parseInt( ratStr ) );
355        } else {
356            // slash, need to parse the two parts separately
357            int n = Integer.parseInt(ratStr.substring(0, slashLoc));
358            int d = Integer.parseInt(ratStr.substring(slashLoc+1,
359                                                      ratStr.length()));
360            return new RatNum(n, d);
361        }
362    }
363
364    /**
365     * Declare a serialization version number. This field is necessary because
366     * our parent class (Number) implements Serializable; see the api docs for
367     * java.lang.Serializable for more details.
368     */
369    private static final long serialVersionUID = -8593953691277016262L;
370}