001    package 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.")
021    public 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        /** @effects Constructs a new RatNum = n. */
050        public RatNum(int n) {
051            numer = n;
052            denom = 1;
053            checkRep();
054        }
055    
056        /** @effects If d = 0, constructs a new RatNum = NaN.  Else
057            constructs a new RatNum = (n / d).
058        */
059        public RatNum(int n, int d) {
060            // special case for zero denominator; gcd(n,d) requires d != 0
061            if (d == 0) {
062                numer = n;
063                denom = 0;
064    
065            } else {
066    
067                // reduce ratio to lowest terms
068                int g = gcd(n,d);
069                n = n / g;
070                d = d / g;
071    
072                if (d < 0) {
073                    numer = -n;
074                    denom = -d;
075                } else {
076                    numer = n;
077                    denom = d;
078                }
079            }
080            checkRep();
081        }
082    
083        /**
084         * Checks that the representation invariant holds (if any).
085         **/
086        // Throws a RuntimeException if the rep invariant is violated.
087        private void checkRep() throws RuntimeException {
088            if (denom < 0)
089                throw new RuntimeException("Denominator of a RatNum cannot be less than zero");
090    
091            if (denom > 0) {
092                int thisGcd = gcd (numer, denom);
093                if (thisGcd != 1 && thisGcd != -1) {
094                    throw new RuntimeException("RatNum not in lowest form");
095                }
096            }
097        }
098    
099        /** Returns true if this is NaN
100            @return true iff this is NaN (not-a-number)
101        */
102        public boolean isNaN() {
103            return (denom == 0);
104        }
105    
106        /** Returns true if this is negative.
107            @return true iff this < 0. */
108        public boolean isNegative() {
109            return (compareTo(ZERO) < 0);
110        }
111    
112        /** Returns true if this is positive.
113            @return true iff this > 0. */
114        public boolean isPositive() {
115            return (compareTo(ZERO) > 0);
116        }
117    
118        /** Compares two RatNums.
119            @requires rn != null
120            @return a negative number if this < rn,
121            0 if this = rn,
122            a positive number if this > rn.
123        */
124        public int compareTo(RatNum rn) {
125            if (this.isNaN() && rn.isNaN()) {
126                return 0;
127            } else if (this.isNaN()) {
128                return 1;
129            } else if (rn.isNaN()) {
130                return -1;
131            } else {
132                RatNum diff = this.sub(rn);
133                return diff.numer;
134            }
135        }
136    
137        /** Approximates the value of this rational.
138            @return a double approximation for this.  Note that "NaN" is
139            mapped to {@link Double#NaN}, and the {@link Double#NaN} value
140            is treated in a special manner by several arithmetic operations,
141            such as the comparison and equality operators.  See the
142            <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.2.3">
143            Java Language Specification, section 4.2.3</a>, for more details.
144        */
145        public double doubleValue() {
146            if (isNaN()) {
147                return Double.NaN;
148            } else {
149                // convert int values to doubles before dividing.
150                return ((double)numer) / ((double)denom);
151            }
152        }
153    
154        /** Returns an integer approximation for this.  The rational number
155            is rounded to the nearest integer.
156         */
157        public int intValue() {
158            // round to nearest by adding +/- .5 before truncating division.
159            // we expect the implementation to use "round half away from zero".
160            // for more info, see http://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero
161    
162            // note that even though the result is guaranteed to fit in an
163            // int, we need to use longs for the computation.
164            if (numer >= 0) {
165                return (int) (((long)numer + (denom/2)) / denom);
166            } else {
167                return (int) (((long)numer - (denom/2)) / denom);
168            }
169        }
170    
171        /** Returns a float approximation for this.  This method is
172            specified by our superclass, Number.
173         */
174        public float floatValue() {
175            return (float) doubleValue();
176        }
177    
178    
179        /** Returns a long approximation for this.  This method is
180            specified by our superclass, Number.  The value returned
181            is rounded to the nearest long.
182         */
183        public long longValue() {
184            return intValue();
185        }
186    
187    
188        // in the implementation comments for the following methods, <this>
189        // is notated as "a/b" and <arg> likewise as "x/y"
190    
191        /** Returns the additive inverse of this RatNum.
192            @return a Rational equal to (0 - this).
193        */
194        public RatNum negate() {
195            return new RatNum(- this.numer, this.denom);
196        }
197    
198        /** Addition operation.
199            @requires arg != null
200            @return a RatNum equal to (this + arg).
201            If either argument is NaN, then returns NaN.
202        */
203        public RatNum add(RatNum arg) {
204            // a/b + x/y = ay/by + bx/by = (ay + bx)/by
205            return new RatNum(this.numer*arg.denom + arg.numer*this.denom,
206                              this.denom*arg.denom);
207        }
208    
209        /** Subtraction operation.
210            @requires arg != null
211            @return a RatNum equal to (this - arg).
212            If either argument is NaN, then returns NaN.
213        */
214        public RatNum sub(RatNum arg) {
215            // a/b - x/y = a/b + -x/y
216            return this.add(arg.negate());
217        }
218    
219        /** Multiplication operation.
220            @requires arg != null
221            @return a RatNum equal to (this * arg).
222            If either argument is NaN, then returns NaN.
223        */
224        public RatNum mul(RatNum arg) {
225            // (a/b) * (x/y) = ax/by
226            return new RatNum(this.numer*arg.numer,
227                              this.denom*arg.denom );
228        }
229    
230        /** Division operation.
231            @requires arg != null
232            @return a RatNum equal to (this / arg).
233            If arg is zero, or if either argument is NaN, then returns NaN.
234        */
235        public RatNum div(RatNum arg) {
236            // (a/b) / (x/y) = ay/bx
237            if (arg.isNaN()) {
238                return arg;
239            } else {
240                return new RatNum(this.numer*arg.denom,
241                                  this.denom*arg.numer);
242            }
243        }
244    
245        /** Returns the greatest common divisor of 'a' and 'b'.
246            @requires b != 0
247            @return d such that a % d = 0 and b % d = 0
248        */
249        private static int gcd(int a, int b) {
250            // Euclid's method
251            if (b == 0)
252                return 0;
253            while (b != 0) {
254                int tmp = b;
255                b = a % b;
256                a = tmp;
257            }
258            return a;
259        }
260    
261        /** Standard hashCode function.
262            @return an int that all objects equal to this will also
263            return.
264        */
265        @Override
266        public int hashCode() {
267            // all instances that are NaN must return the same hashcode;
268            if (this.isNaN()) {
269                return 0;
270            }
271            return this.numer*2 + this.denom*3;
272        }
273    
274        /** Standard equality operation.
275            @return true if and only if 'obj' is an instance of a RatNum
276            and 'this' and 'obj' represent the same rational number.  Note
277            that NaN = NaN for RatNums.
278        */
279        @Override
280        public boolean equals(/*@Nullable*/ Object obj) {
281            if (obj instanceof RatNum) {
282                RatNum rn = (RatNum) obj;
283    
284                // special case: check if both are NaN
285                if (this.isNaN() && rn.isNaN()) {
286                    return true;
287                } else {
288                    return this.numer == rn.numer && this.denom == rn.denom;
289                }
290            } else {
291                return false;
292            }
293        }
294    
295        /** @return a String representing this, in reduced terms.
296            The returned string will either be "NaN", or it will take on
297            either of the forms "N" or "N/M", where N and M are both
298            integers in decimal notation and M != 0.
299        */
300        @Override
301        public String toString() {
302            // using '+' as String concatenation operator in this method
303            if (isNaN()) {
304                return "NaN";
305            } else if (denom != 1) {
306                return numer + "/" + denom;
307            } else {
308                return Integer.toString(numer);
309            }
310        }
311    
312        /** Makes a RatNum from a string describing it.
313            @requires 'ratStr' is an instance of a string, with no spaces,
314            of the form: <UL>
315            <LI> "NaN"
316            <LI> "N/M", where N and M are both integers in
317            decimal notation, and M != 0, or
318            <LI> "N", where N is an integer in decimal
319            notation.
320            </UL>
321            @returns NaN if ratStr = "NaN".  Else returns a
322            RatNum r = ( N / M ), letting M be 1 in the case
323            where only "N" is passed in.
324        */
325        public static RatNum valueOf(String ratStr) {
326            int slashLoc = ratStr.indexOf('/');
327            if (ratStr.equals("NaN")) {
328                return new RatNum(1,0);
329            } else if (slashLoc == -1) {
330                // not NaN, and no slash, must be an Integer
331                return new RatNum( Integer.parseInt( ratStr ) );
332            } else {
333                // slash, need to parse the two parts seperately
334                int n = Integer.parseInt(ratStr.substring(0, slashLoc));
335                int d = Integer.parseInt(ratStr.substring(slashLoc+1,
336                                                          ratStr.length()));
337                return new RatNum(n, d);
338            }
339        }
340    
341        /**
342         * Declare a serialization version number. This field is necessary because
343         * our parent class (Number) implements Serializable; see the api docs for
344         * java.lang.Serializable for more details.
345         */
346        private static final long serialVersionUID = -8593953691277016262L;
347    }