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}