001 package ps1;
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://java.sun.com/docs/books/jls/second_edition/html/typesValues.doc.html#9208">
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 }