import java.lang.Double; /** RatNum represents an immutable rational number. It includes all of the elements of the set of rationals, as well as the special "NaN" (not-a-number) element that results from division by zero.
The "NaN" element is special in many ways. Any arithmetic operation (such as addition) involving "NaN" will return "NaN". With respect to comparison operations, such as less-than, "NaN" is considered equal to itself, and larger than all other rationals.
Examples of RatNums include "-1/13", "53/7", "4", "NaN", and "0".
*/
public class RatNum {
private int numer;
private int denom;
// Abstraction Function:
// A RatNum r is NaN if r.denom = 0, (r.numer / r.denom) otherwise.
// (An abstraction function explains what the state of the fields in a
// RatNum represents. In this case, a rational number can be
// understood as the result of dividing two integers, or not-a-number
// if we would be dividing by zero.)
// Representation invariant for every RatNum r:
// (r.denom >= 0) &&
// (r.denom > 0 ==> there does not exist integer i > 1 such that
// r.numer mod i = 0 and r.denom mod i = 0)
// (A representation invariant tells us something that is true for all
// instances of a RatNum; in this case, that the denominator is always
// greater than zero and that if the denominator is non-zero, the
// fraction represented is in reduced form.)
/** @effects Constructs a new RatNum = n. */
public RatNum(int n) {
numer = n;
denom = 1;
checkRep();
}
/** @effects If d = 0, constructs a new RatNum = NaN. Else
constructs a new RatNum = (n / d).
*/
public RatNum(int n, int d) {
// special case for zero denominator; gcd(n,d) requires d != 0
if (d == 0) {
numer = n;
denom = 0;
checkRep();
return;
}
// reduce ratio to lowest terms
int g = gcd(n,d);
numer = n / g;
denom = d / g;
if (denom < 0) {
numer = -numer;
denom = -denom;
checkRep();
}
}
/** Checks to see if the representation invariant is being violated and if so, throws RuntimeException
@throws RuntimeException if representation invariant is violated
*/
private void checkRep() throws RuntimeException {
if(denom < 0)
throw new RuntimeException("Denominator of a RatNum cannot be less than zero");
if(denom > 0) {
int thisGcd = gcd (numer, denom);
if (thisGcd != 1 && thisGcd != -1) {
throw new RuntimeException("RatNum not in lowest form");
}
}
}
/** @return true iff this is NaN (not-a-number) */
public boolean isNaN() {
checkRep();
return (denom == 0);
}
/** @return true iff this < 0. */
public boolean isNegative() {
checkRep();
return (compareTo(new RatNum(0)) < 0);
}
/** @return true iff this > 0. */
public boolean isPositive() {
checkRep();
return (compareTo(new RatNum(0)) > 0);
}
/** @requires rn != null
@return a negative number if this < rn,
0 if this = rn,
a positive number if this > rn.
*/
public int compareTo(RatNum rn) {
checkRep();
if (this.isNaN() && rn.isNaN()) {
checkRep();
return 0;
} else if (this.isNaN()) {
checkRep();
return 1;
} else if (rn.isNaN()) {
checkRep();
return -1;
} else {
RatNum diff = this.sub(rn);
checkRep();
return diff.numer;
}
}
/** Approximates the value of this rational.
@return a double approximation for this. Note that "NaN" is
mapped to {@link Double#NaN}, and the {@link Double#NaN} value
is treated in a special manner by several arithmetic operations,
such as the comparison and equality operators. See the
Java Language Specification, section 4.2.3, for more details.
*/
public double approx() {
checkRep();
if (isNaN()) {
checkRep();
return Double.NaN;
} else {
// convert int values to doubles before dividing.
checkRep();
return ((double)numer) / ((double)denom);
}
}
/** @return a String representing this, in reduced terms.
The returned string will either be "NaN", or it will take on
either of the forms "N" or "N/M", where N and M are both
integers in decimal notation and M != 0.
*/
public String unparse() {
// using '+' as String concatenation operator in this method
checkRep();
if (isNaN()) {
checkRep();
return "NaN";
} else if (denom != 1) {
checkRep();
return numer + "/" + denom;
} else {
checkRep();
return Integer.toString(numer);
}
}
// in the implementation comments for the following methods,
@returns NaN if ratStr = "NaN". Else returns a
RatNum r = ( N / M ), letting M be 1 in the case
where only "N" is passed in.
*/
public static RatNum parse(String ratStr) {
int slashLoc = ratStr.indexOf('/');
if (ratStr.equals("NaN")) {
return new RatNum(1,0);
} else if (slashLoc == -1) {
// not NaN, and no slash, must be an Integer
return new RatNum( Integer.parseInt( ratStr ) );
} else {
// slash, need to parse the two parts seperately
int n = Integer.parseInt(ratStr.substring(0, slashLoc));
int d = Integer.parseInt(ratStr.substring(slashLoc+1,
ratStr.length()));
return new RatNum(n, d);
}
}
}