/*************************************** * ViPER * * The Video Processing * * Evaluation Resource * * * * Distributed under the GPL license * * Terms available at gnu.org. * * * * Copyright University of Maryland, * * College Park. * ***************************************/ package edu.umd.cfar.lamp.viper.geometry; import java.math.*; import java.util.*; /** * Represents numbers as a fraction with two BigIntegers. Useful for * intersecting polygons and the like. */ public class Rational extends Number implements Comparable { /** * 1 */ private static final long serialVersionUID = 1L; /** * This array holds the numerator and denominator */ private BigInteger[] bignum = new BigInteger[2]; /** * Tries to reduce the fraction, maybe moving from BigIntegers to ints. * @return <code>this</code> after reduction. */ private Rational reduce() { // handle cases with zeroes if ((bignum[0].equals(BigInteger.ZERO)) || (bignum[1].equals(BigInteger.ZERO))) { int compVal = bignum[0].compareTo(BigInteger.ZERO); if (!bignum[1].equals(BigInteger.ZERO)) { bignum[1] = BigInteger.ONE; } else if (compVal < 0) { bignum[0] = BigInteger.ONE.negate(); bignum[1] = BigInteger.ZERO; } else if (compVal > 0) { bignum[0] = BigInteger.ONE; bignum[1] = BigInteger.ZERO; } else { bignum[1] = bignum[1] = BigInteger.ZERO; } return this; } // keep the negative signs on the top if (bignum[1].compareTo(BigInteger.ZERO) < 0) { bignum[0] = bignum[0].negate(); bignum[1] = bignum[1].negate(); } // Reduce stuff if (!bignum[1].equals(BigInteger.ONE)) { BigInteger gcd = bignum[0].gcd(bignum[1]); if (!gcd.equals(BigInteger.ONE)) { bignum[0] = bignum[0].divide(gcd); bignum[1] = bignum[1].divide(gcd); } } return this; } /** * Constructs a new Rational. Defaults to NaN. */ public Rational() { bignum[0] = bignum[1] = BigInteger.ZERO; } /** * Constructs a new Rational with the given integer value. * @param numerator Integer value to give the Rational. */ public Rational(long numerator) { bignum[0] = BigInteger.valueOf(numerator); bignum[1] = BigInteger.ONE; } /** * Constructs a new Rational with the given value. * @param numerator The number on top of the fraction. * @param denominator The number on bottom. */ public Rational(long numerator, long denominator) { bignum[0] = BigInteger.valueOf(numerator); bignum[1] = BigInteger.valueOf(denominator); reduce(); } /** * Constructs a new Rational with the given value. (Copy Constructor.) * @param old Rational value to give the new one. */ public Rational(Rational old) { bignum[0] = old.bignum[0]; bignum[1] = old.bignum[1]; } /** * Set this to the given value. * @param old Rational to set this to. * @return <code>this</code> after getting set. */ public Rational setTo(Rational old) { bignum[0] = old.bignum[0]; bignum[1] = old.bignum[1]; return this; } /** * Set this to the given value. * * @param numerator * An integer value to set this to. * @return <code>this</code> after getting set. */ public Rational setTo(long numerator) { bignum[0] = BigInteger.valueOf(numerator); bignum[1] = BigInteger.ONE; return this; } /** * Set this to the given value. * * @param numerator * The numerator of the fraction. * @param denominator * The denominator of the fraction. * @return <code>this</code> after getting set. */ public Rational setTo(long numerator, long denominator) { bignum[0] = BigInteger.valueOf(numerator); bignum[1] = BigInteger.valueOf(denominator); reduce(); return this; } /** * Gets a string representation. Either -inf, +inf, NAN, or the numerator / * denominator. * * @return The fraction as a String. */ public String toString() { if (bignum[1].equals(BigInteger.ONE)) { return bignum[0].toString(); } else if (bignum[1].equals(BigInteger.ZERO)) { switch (bignum[0].compareTo(BigInteger.ZERO)) { case -1: return "-inf"; case 0: return "NaN"; case 1: return "+inf"; default: return "badValue"; } } else return ("(" + bignum[0] + " / " + bignum[1] + ")"); } /** * Get a hashcode for this number. * * @return The numerator xored with the denominator. */ public int hashCode() { return bignum[0].xor(bignum[1]).intValue(); } /** * Tests the equality of two Rationals or Numbers. * * @param other * The object to compare against this rational number. * @return <code>true</code> if they are equal. */ public boolean equals(Object other) { if (this == other) { return true; } if (other instanceof Rational) { Rational rother = (Rational) other; return (bignum[0].equals(rother.bignum[0]) && bignum[1] .equals(rother.bignum[1])); } if (other instanceof Number) { return ((Number) other).doubleValue() == doubleValue(); } return false; } /** * Tests the equality of this Rational with an int. * * @param other * The int to compare against this rational number. * @return <code>true</code> if they are equal. */ public boolean equals(int other) { if (!bignum[1].equals(BigInteger.ONE)) return false; else return bignum[0].equals(BigInteger.valueOf(other)); } /** * Tests to see if the number is zero. * @return <code>true</code> if the number is zero */ public boolean isZero() { return bignum[0].equals(BigInteger.ZERO) && !bignum[1].equals(BigInteger.ZERO); } /** * Tests to see if the number is negative. * @return <code>true</code> if the number is less than zero */ public boolean isNegative() { return bignum[0].compareTo(BigInteger.ZERO) < 0; } /** * Tests to see if the number is positive. * @return <code>true</code> if the number is greater than zero */ public boolean isPositive() { return bignum[0].compareTo(BigInteger.ZERO) > 0; } /** * Checks to see if this is less than another Rational. * @param other the Rational to test against. * @return <code>true</code> iff this is less than the other. */ public boolean lessThan(Rational other) { BigInteger[] otherNum = other.bignum; BigInteger[] thisNum = bignum; if ((thisNum[0].equals(BigInteger.ZERO) && thisNum[1] .equals(BigInteger.ZERO)) || (otherNum[0].equals(BigInteger.ZERO) && otherNum[1] .equals(BigInteger.ZERO))) { throw new ArithmeticException("0/0 error: " + this + " < " + other); } else if (thisNum[1].equals(BigInteger.ZERO) && otherNum[1].equals(BigInteger.ZERO)) { return (thisNum[0].compareTo(otherNum[0]) < 0); } else if (otherNum[1].equals(BigInteger.ZERO)) { return (otherNum[0].compareTo(BigInteger.ZERO) > 0); } else if (thisNum[1].equals(BigInteger.ZERO)) { return (thisNum[0].compareTo(BigInteger.ZERO) < 0); } else if (thisNum[1].equals(otherNum[1])) { return thisNum[0].compareTo(otherNum[0]) < 0; } else { if (thisNum[1].signum() < 0) { this.reduce(); } if (otherNum[1].signum() < 0) { other.reduce(); } return (thisNum[0].multiply(otherNum[1]).compareTo( otherNum[0].multiply(thisNum[1])) < 0); } } /** * Checks to see if this is less than an int. * @param other the int to test against. * @return <code>true</code> iff this is less than the other. */ public boolean lessThan(int other) { return lessThan(new Rational(other)); } /** * Checks to see if this is greater than an int. * @param other the int to test against. * @return <code>true</code> iff this is greater than the other. */ public boolean greaterThan(int other) { return this.greaterThan(new Rational(other)); } /** * Checks to see if this is greater than another Rational. * @param other the number to test against. * @return <code>true</code> iff this is greater than the other. */ public boolean greaterThan(Rational other) { return other.lessThan(this); } /** * Checks to see if this is less than or equal to another Rational. * @param other the number to test against. * @return <code>true</code> iff this is less than or equal to the other. */ public boolean lessThanEqualTo(Rational other) { return !this.greaterThan(other); } /** * Checks to see if this is greater than or equal to another Rational. * @param other the number to test against. * @return <code>true</code> iff this is greater than or equal to the other. */ public boolean greaterThanEqualTo(Rational other) { return !this.lessThan(other); } ///////// Comparable ////////// /** * Compares to the other number. * @param o the number to compare against * @return zero if they are equal, negative if o is greater, and positive if this is greater */ public int compareTo(Object o) { if (o == this) { return 0; } else if (o instanceof Rational) { Rational result = new Rational(); Rational.minus(this, (Rational) o, result); return result.isZero() ? 0 : result.isPositive() ? 1 : -1; } else { double diff = doubleValue() - ((Number) o).doubleValue(); return (diff == 0) ? 0 : (diff > 0) ? 1 : -1; } } /////////// Number //////////// /** * Gets the <code>double</code> approximation of this rational. * @return a <code>double</code> close to the value of <code>this</code> */ public double doubleValue() { BigInteger[] quotAndRem = bignum[0].divideAndRemainder(bignum[1]); double retval = quotAndRem[0].doubleValue(); quotAndRem[1] = quotAndRem[1].multiply( BigInteger.valueOf(Integer.MAX_VALUE)).divide(bignum[1]); retval += quotAndRem[1].doubleValue() / Integer.MAX_VALUE; return retval; } /** * Gets the <code>float</code> approximation of this rational. * @return a <code>float</code> close to the value of <code>this</code> */ public float floatValue() { return (float) doubleValue(); } /** * Gets the <code>byte</code> approximation of this rational. * @return the <code>byte</code> closest to the value of <code>this</code> */ public byte byteValue() { return (byte) intValue(); } /** * Gets the <code>short</code> approximation of this rational. * @return the <code>short</code> closest to the value of <code>this</code> */ public short shortValue() { return (short) intValue(); } /** * Gets the <code>int</code> approximation of this rational. * @return the <code>int</code> closest to the value of <code>this</code> */ public int intValue() { return (int) Math.floor(doubleValue()); } /** * Compute the floor as an integer. * @return the floor. This is equivalent to * num / div, where they are both integers. */ public BigInteger floor() { return bignum[0].divide(bignum[1]); } /** * Compute the ceiling as an integer. * @return the ceiling */ public BigInteger ceiling() { BigInteger[] divAndRem = bignum[0].divideAndRemainder(bignum[1]); if (divAndRem[1].intValue() == 0) { return divAndRem[0]; } return divAndRem[0].add(BigInteger.ONE); } /** * Gets the <code>long</code> approximation of this rational. * @return the <code>long</code> closest to the value of <code>this</code> */ public long longValue() { return bignum[0].divide(bignum[1]).longValue(); } ///// Mathematical Functions /////// /** * Subtracts one Rational from another and stores the result into a third. * Does not clobber result, so you can pass it all the same reference and * end up with twice the original just fine. * * @param first * A Rational to add to. * @param second * A Rational to add. * @param result * Where to store the result. */ public static void plus(Rational first, Rational second, Rational result) { BigInteger[] A; BigInteger[] B; A = first.bignum; B = second.bignum; BigInteger tempDenom = A[1].multiply(B[1]); result.bignum[0] = (A[0].multiply(B[1]).add(B[0].multiply(A[1]))); result.bignum[1] = tempDenom; result.reduce(); } /** * Subtracts one Rational from another and stores the result into a third. * Keeps the order, so you can pass it all the same reference and end up * with zero just fine. * * @param first a Rational to subtract something from. * @param second a Rational to subtract from the first. * @param difference where to store the result. */ public static void minus(Rational first, Rational second, Rational difference) { Rational.plus(first, new Rational(second).negate(), difference); } /** * Multiplies two Rationals and stores the result into a third. Keeps the * order, so you can pass it all the same reference and end up with the * square just fine. * * @param first * A Rational to multiply. * @param second * Another Rational to multiply. * @param result * Where to store the result. */ public static void multiply(Rational first, Rational second, Rational result) { BigInteger[] A; BigInteger[] B; A = first.bignum; B = second.bignum; result.bignum[0] = A[0].multiply(B[0]); result.bignum[1] = A[1].multiply(B[1]); result.reduce(); } /** * Multiplies a Rational with a long and stores the result into another * Rational. Keeps the order, so you can pass it all the same reference and * end up fine. * * @param first * A Rational to multiply. * @param second * A long to multiply. * @param result * Where to store the result. */ public static void multiply(Rational first, long second, Rational result) { result.bignum[0] = first.bignum[0].multiply(BigInteger.valueOf(second)); result.bignum[1] = first.bignum[1]; result.reduce(); } /** * Divides two Rationals and stores the result in a third. Keeps the order, * so you can pass it all the same reference and end up with 1 just fine. * * @param numerator * The thing on top, or divisor. * @param denominator * The thing on the bottom, or dividend. * @param quotient * Where to store the answer. */ public static void divide(Rational numerator, Rational denominator, Rational quotient) { BigInteger[] A; BigInteger[] B; A = numerator.bignum; B = denominator.bignum; BigInteger temp = A[0].multiply(B[1]); quotient.bignum[1] = A[1].multiply(B[0]); quotient.bignum[0] = temp; quotient.reduce(); } /** * Turns negates fraction upside-down, by negating its numerator. * * @return Itself, now the opposite of what it was. */ public Rational negate() { bignum[0] = bignum[0].negate(); return this; } /** * Turns this fraction upside-down, by turning it to its reciprocal. * * @return Itself, now upside-down. */ public Rational reciprocate() { BigInteger temp = bignum[1]; bignum[1] = bignum[0]; bignum[0] = temp; if (bignum[1].signum() < 0) { bignum[0] = bignum[0].negate(); bignum[1] = bignum[1].negate(); } return this; } /** * Changes <code>this</code> to refer to its abolute value * @return <code>this</code> */ public Rational abs() { bignum[0] = bignum[0].abs(); bignum[1] = bignum[1].abs(); return this; } /** * Square a Rational. Sets <code>this</code> to its square. * * @return <code>this</code>, now squared. */ public Rational square() { Rational.multiply(this, this, this); reduce(); // Needed to check for overflow return this; } /** * @param valStr * @return */ public static Rational parseRational(String valStr) { StringTokenizer st = new StringTokenizer(valStr, " ./", true); if (!st.hasMoreTokens()) { return new Rational(0); } String first = st.nextToken(); String wholePart = ""; String fracPart = ""; boolean emptyDec = ".".equals(first); boolean dec = emptyDec; if (!dec) { wholePart = first; if (st.hasMoreTokens()) { String next = st.nextToken(); if ("/".equals(next)) { fracPart = wholePart; wholePart = ""; } else if (" ".equals(next)) { fracPart = st.nextToken(); st.nextToken(); } else { // ".".equals(next) dec = true; } } else { } } Rational r = new Rational(); if (dec) { fracPart = st.nextToken(); BigInteger denom = new BigInteger("10").pow(fracPart.length()); BigInteger num = new BigInteger(fracPart); r.bignum[0] = num; r.bignum[1] = denom; } else if (!"".equals(fracPart)) { r.bignum[0] = new BigInteger(fracPart); r.bignum[1] = st.hasMoreTokens() ? new BigInteger(st.nextToken()) : BigInteger.ONE; } else { r.bignum[0] = BigInteger.ZERO; r.bignum[1] = BigInteger.ONE; } if (wholePart.length() > 0) { BigInteger top = new BigInteger(wholePart); top = top.multiply(r.bignum[1]); r.bignum[0] = r.bignum[0].add(top); } r.reduce(); return r; } }