From 007bfb14d2d720cdd699cfbb36ce206246901cef Mon Sep 17 00:00:00 2001
From: Igor Murashkin A value is considered to be within this range if it's {@code >=} then
+ * the lower endpoint and {@code <=} to the upper endpoint (using the {@link Comparable}
+ * interface. An immutable data type representation a rational number. Contains a pair of {@code int}s representing the numerator and denominator of a
* Rational number. A {@code NaN} value is considered to be equal to itself (that is {@code NaN.equals(NaN)}
+ * will return {@code true}; it is always greater than any non-{@code NaN} value (that is
+ * {@code NaN.compareTo(notNaN)} will return a number greater than {@code 0}). Equivalent to constructing a new rational with both the numerator and denominator
+ * equal to {@code 0}. Equivalent to constructing a new rational with a positive numerator and a denominator
+ * equal to {@code 0}. Equivalent to constructing a new rational with a negative numerator and a denominator
+ * equal to {@code 0}. Equivalent to constructing a new rational with a numerator equal to {@code 0} and
+ * any non-zero denominator. Increment each time the fields change in any way. Create a Rational with a given numerator and denominator. Create a {@code Rational} with a given numerator and denominator. The signs of the numerator and the denominator may be flipped such that the denominator
- * is always positive.
A rational value with a 0-denominator may be constructed, but will have similar semantics - * as float {@code NaN} and {@code INF} values. For {@code NaN}, - * both {@link #getNumerator} and {@link #getDenominator} functions will return 0. For - * positive or negative {@code INF}, only the {@link #getDenominator} will return 0.
+ *For example, + *
The numerator will always return {@code 1} if this rational represents + * infinity (that is, the denominator is {@code 0}).
*/ public int getNumerator() { - if (mDenominator == 0) { - return 0; - } return mNumerator; } /** * Gets the denominator of the rational + * + *The denominator may return {@code 0}, in which case the rational may represent + * positive infinity (if the numerator was positive), negative infinity (if the numerator + * was negative), or {@code NaN} (if the numerator was {@code 0}).
+ * + *The denominator will always return {@code 1} if the numerator is {@code 0}. */ public int getDenominator() { return mDenominator; } - private boolean isNaN() { + /** + * Indicates whether this rational is a Not-a-Number (NaN) value. + * + *
A {@code NaN} value occurs when both the numerator and the denominator are {@code 0}.
+ * + * @return {@code true} if this rational is a Not-a-Number (NaN) value; + * {@code false} if this is a (potentially infinite) number value + */ + public boolean isNaN() { return mDenominator == 0 && mNumerator == 0; } - private boolean isInf() { + /** + * Indicates whether this rational represents an infinite value. + * + *An infinite value occurs when the denominator is {@code 0} (but the numerator is not).
+ * + * @return {@code true} if this rational is a (positive or negative) infinite value; + * {@code false} if this is a finite number value (or {@code NaN}) + */ + public boolean isInfinite() { + return mNumerator != 0 && mDenominator == 0; + } + + /** + * Indicates whether this rational represents a finite value. + * + *A finite value occurs when the denominator is not {@code 0}; in other words + * the rational is neither infinity or {@code NaN}.
+ * + * @return {@code true} if this rational is a (positive or negative) infinite value; + * {@code false} if this is a finite number value (or {@code NaN}) + */ + public boolean isFinite() { + return mDenominator != 0; + } + + /** + * Indicates whether this rational represents a zero value. + * + *A zero value is a {@link #isFinite finite} rational with a numerator of {@code 0}.
+ * + * @return {@code true} if this rational is finite zero value; + * {@code false} otherwise + */ + public boolean isZero() { + return isFinite() && mNumerator == 0; + } + + private boolean isPosInf() { return mDenominator == 0 && mNumerator > 0; } @@ -82,12 +209,12 @@ public final class Rational { /** *Compare this Rational to another object and see if they are equal.
* - *A Rational object can only be equal to another Rational object (comparing against any other - * type will return false).
+ *A Rational object can only be equal to another Rational object (comparing against any + * other type will return {@code false}).
* *A Rational object is considered equal to another Rational object if and only if one of - * the following holds
: - *{@code
- * (new Rational(1, 2)).equals(new Rational(1, 2)) == true // trivially true
- * (new Rational(2, 3)).equals(new Rational(1, 2)) == false // trivially false
- * (new Rational(1, 2)).equals(new Rational(2, 4)) == true // true after reduction
- * (new Rational(0, 0)).equals(new Rational(0, 0)) == true // NaN.equals(NaN)
- * (new Rational(1, 0)).equals(new Rational(5, 0)) == true // both are +infinity
- * (new Rational(1, 0)).equals(new Rational(-1, 0)) == false // +infinity != -infinity
+ * (new Rational(1, 2)).equals(new Rational(1, 2)) == true // trivially true
+ * (new Rational(2, 3)).equals(new Rational(1, 2)) == false // trivially false
+ * (new Rational(1, 2)).equals(new Rational(2, 4)) == true // true after reduction
+ * (new Rational(0, 0)).equals(new Rational(0, 0)) == true // NaN.equals(NaN)
+ * (new Rational(1, 0)).equals(new Rational(5, 0)) == true // both are +infinity
+ * (new Rational(1, 0)).equals(new Rational(-1, 0)) == false // +infinity != -infinity
* }
*
* @param obj a reference to another object
@@ -110,41 +237,31 @@ public final class Rational {
*/
@Override
public boolean equals(Object obj) {
- if (obj == null) {
- return false;
- } else if (obj instanceof Rational) {
- Rational other = (Rational) obj;
- if (mDenominator == 0 || other.mDenominator == 0) {
- if (isNaN() && other.isNaN()) {
- return true;
- } else if (isInf() && other.isInf() || isNegInf() && other.isNegInf()) {
- return true;
- } else {
- return false;
- }
- } else if (mNumerator == other.mNumerator && mDenominator == other.mDenominator) {
- return true;
- } else {
- int thisGcd = gcd();
- int otherGcd = other.gcd();
-
- int thisNumerator = mNumerator / thisGcd;
- int thisDenominator = mDenominator / thisGcd;
-
- int otherNumerator = other.mNumerator / otherGcd;
- int otherDenominator = other.mDenominator / otherGcd;
-
- return (thisNumerator == otherNumerator && thisDenominator == otherDenominator);
- }
- }
- return false;
+ return obj instanceof Rational && equals((Rational) obj);
+ }
+
+ private boolean equals(Rational other) {
+ return (mNumerator == other.mNumerator && mDenominator == other.mDenominator);
}
+ /**
+ * Return a string representation of this rational, e.g. {@code "1/2"}.
+ *
+ * The following rules of conversion apply: + *
Visible for testing only.
+ * + * @param numerator the numerator in a fraction + * @param denominator the denominator in a fraction + * * @return An int value representing the gcd. Always positive. * @hide */ - public int gcd() { - /** + public static int gcd(int numerator, int denominator) { + /* * Non-recursive implementation of Euclid's algorithm: * * gcd(a, 0) := a * gcd(a, b) := gcd(b, a mod b) * */ - - int a = mNumerator; - int b = mDenominator; + int a = numerator; + int b = denominator; while (b != 0) { int oldB = b; @@ -201,4 +323,221 @@ public final class Rational { return Math.abs(a); } + + /** + * Returns the value of the specified number as a {@code double}. + * + *The {@code double} is calculated by converting both the numerator and denominator + * to a {@code double}; then returning the result of dividing the numerator by the + * denominator.
+ * + * @return the divided value of the numerator and denominator as a {@code double}. + */ + @Override + public double doubleValue() { + double num = mNumerator; + double den = mDenominator; + + return num / den; + } + + /** + * Returns the value of the specified number as a {@code float}. + * + *The {@code float} is calculated by converting both the numerator and denominator + * to a {@code float}; then returning the result of dividing the numerator by the + * denominator.
+ * + * @return the divided value of the numerator and denominator as a {@code float}. + */ + @Override + public float floatValue() { + float num = mNumerator; + float den = mDenominator; + + return num / den; + } + + /** + * Returns the value of the specified number as a {@code int}. + * + *{@link #isInfinite Finite} rationals are converted to an {@code int} value + * by dividing the numerator by the denominator; conversion for non-finite values happens + * identically to casting a floating point value to an {@code int}, in particular: + * + *
+ *
{@link #isInfinite Finite} rationals are converted to an {@code long} value + * by dividing the numerator by the denominator; conversion for non-finite values happens + * identically to casting a floating point value to a {@code long}, in particular: + * + *
+ *
{@link #isInfinite Finite} rationals are converted to a {@code short} value + * identically to {@link #intValue}; the {@code int} result is then truncated to a + * {@code short} before returning the value.
+ * + * @return the divided value of the numerator and denominator as a {@code short}. + */ + @Override + public short shortValue() { + return (short) intValue(); + } + + /** + * Compare this rational to the specified rational to determine their natural order. + * + *{@link #NaN} is considered to be equal to itself and greater than all other + * {@code Rational} values. Otherwise, if the objects are not {@link #equals equal}, then + * the following rules apply:
+ * + *+ * adb shell am instrument \ + * -e class 'com.android.mediaframeworktest.unit.RangeTest' \ + * -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner + *+ */ +public class RangeTest extends junit.framework.TestCase { + + @SmallTest + public void testConstructor() { + // Trivial, same range + Range
* adb shell am instrument \ @@ -27,6 +38,22 @@ import android.util.Rational; **/ public class RationalTest extends junit.framework.TestCase { + + /** (1,1) */ + private static final Rational UNIT = new Rational(1, 1); + + /** + * Test @hide greatest common divisior functionality that cannot be tested in CTS. + */ + @SmallTest + public void testGcd() { + assertEquals(1, Rational.gcd(1, 2)); + assertEquals(1, Rational.gcd(2, 3)); + assertEquals(78, Rational.gcd(5*78, 7*78)); + assertEquals(1, Rational.gcd(-1, 2)); + assertEquals(1, Rational.gcd(-2, 3)); + } + @SmallTest public void testConstructor() { @@ -52,12 +79,12 @@ public class RationalTest extends junit.framework.TestCase { // Infinity. r = new Rational(1, 0); - assertEquals(0, r.getNumerator()); + assertEquals(1, r.getNumerator()); assertEquals(0, r.getDenominator()); // Negative infinity. r = new Rational(-1, 0); - assertEquals(0, r.getNumerator()); + assertEquals(-1, r.getNumerator()); assertEquals(0, r.getDenominator()); // NaN. @@ -66,24 +93,6 @@ public class RationalTest extends junit.framework.TestCase { assertEquals(0, r.getDenominator()); } - @SmallTest - public void testGcd() { - Rational r = new Rational(1, 2); - assertEquals(1, r.gcd()); - - Rational twoThirds = new Rational(2, 3); - assertEquals(1, twoThirds.gcd()); - - Rational moreComplicated2 = new Rational(5*78, 7*78); - assertEquals(78, moreComplicated2.gcd()); - - Rational oneHalf = new Rational(-1, 2); - assertEquals(1, oneHalf.gcd()); - - twoThirds = new Rational(-2, 3); - assertEquals(1, twoThirds.gcd()); - } - @SmallTest public void testEquals() { Rational r = new Rational(1, 2); @@ -118,7 +127,13 @@ public class RationalTest extends junit.framework.TestCase { assertEquals(moreComplicated, moreComplicated2); assertEquals(moreComplicated2, moreComplicated); - Rational nan = new Rational(0, 0); + // Zero is always equal to itself + Rational zero2 = new Rational(0, 100); + assertEquals(ZERO, zero2); + assertEquals(zero2, ZERO); + + // NaN is always equal to itself + Rational nan = NaN; Rational nan2 = new Rational(0, 0); assertTrue(nan.equals(nan)); assertTrue(nan.equals(nan2)); @@ -127,9 +142,9 @@ public class RationalTest extends junit.framework.TestCase { assertFalse(r.equals(nan)); // Infinities of the same sign are equal. - Rational posInf = new Rational(1, 0); + Rational posInf = POSITIVE_INFINITY; Rational posInf2 = new Rational(2, 0); - Rational negInf = new Rational(-1, 0); + Rational negInf = NEGATIVE_INFINITY; Rational negInf2 = new Rational(-2, 0); assertEquals(posInf, posInf); assertEquals(negInf, negInf); @@ -148,4 +163,349 @@ public class RationalTest extends junit.framework.TestCase { assertFalse(nan.equals(posInf)); assertFalse(nan.equals(negInf)); } + + @SmallTest + public void testReduction() { + Rational moreComplicated = new Rational(5 * 78, 7 * 78); + assertEquals(new Rational(5, 7), moreComplicated); + assertEquals(5, moreComplicated.getNumerator()); + assertEquals(7, moreComplicated.getDenominator()); + + Rational posInf = new Rational(5, 0); + assertEquals(1, posInf.getNumerator()); + assertEquals(0, posInf.getDenominator()); + assertEquals(POSITIVE_INFINITY, posInf); + + Rational negInf = new Rational(-100, 0); + assertEquals(-1, negInf.getNumerator()); + assertEquals(0, negInf.getDenominator()); + assertEquals(NEGATIVE_INFINITY, negInf); + + Rational zero = new Rational(0, -100); + assertEquals(0, zero.getNumerator()); + assertEquals(1, zero.getDenominator()); + assertEquals(ZERO, zero); + + Rational flipSigns = new Rational(1, -1); + assertEquals(-1, flipSigns.getNumerator()); + assertEquals(1, flipSigns.getDenominator()); + + Rational flipAndReduce = new Rational(100, -200); + assertEquals(-1, flipAndReduce.getNumerator()); + assertEquals(2, flipAndReduce.getDenominator()); + } + + @SmallTest + public void testCompareTo() { + // unit is equal to itself + assertCompareEquals(UNIT, new Rational(1, 1)); + + // NaN is greater than anything but NaN + assertCompareEquals(NaN, new Rational(0, 0)); + assertGreaterThan(NaN, UNIT); + assertGreaterThan(NaN, POSITIVE_INFINITY); + assertGreaterThan(NaN, NEGATIVE_INFINITY); + assertGreaterThan(NaN, ZERO); + + // Positive infinity is greater than any other non-NaN + assertCompareEquals(POSITIVE_INFINITY, new Rational(1, 0)); + assertGreaterThan(POSITIVE_INFINITY, UNIT); + assertGreaterThan(POSITIVE_INFINITY, NEGATIVE_INFINITY); + assertGreaterThan(POSITIVE_INFINITY, ZERO); + + // Negative infinity is smaller than any other non-NaN + assertCompareEquals(NEGATIVE_INFINITY, new Rational(-1, 0)); + assertLessThan(NEGATIVE_INFINITY, UNIT); + assertLessThan(NEGATIVE_INFINITY, POSITIVE_INFINITY); + assertLessThan(NEGATIVE_INFINITY, ZERO); + + // A finite number with the same denominator is trivially comparable + assertGreaterThan(new Rational(3, 100), new Rational(1, 100)); + assertGreaterThan(new Rational(3, 100), ZERO); + + // Compare finite numbers with different divisors + assertGreaterThan(new Rational(5, 25), new Rational(1, 10)); + assertGreaterThan(new Rational(5, 25), ZERO); + + // Compare finite numbers with different signs + assertGreaterThan(new Rational(5, 25), new Rational(-1, 10)); + assertLessThan(new Rational(-5, 25), ZERO); + } + + @SmallTest + public void testConvenienceMethods() { + // isFinite + assertFinite(ZERO, true); + assertFinite(NaN, false); + assertFinite(NEGATIVE_INFINITY, false); + assertFinite(POSITIVE_INFINITY, false); + assertFinite(UNIT, true); + + // isInfinite + assertInfinite(ZERO, false); + assertInfinite(NaN, false); + assertInfinite(NEGATIVE_INFINITY, true); + assertInfinite(POSITIVE_INFINITY, true); + assertInfinite(UNIT, false); + + // isNaN + assertNaN(ZERO, false); + assertNaN(NaN, true); + assertNaN(NEGATIVE_INFINITY, false); + assertNaN(POSITIVE_INFINITY, false); + assertNaN(UNIT, false); + + // isZero + assertZero(ZERO, true); + assertZero(NaN, false); + assertZero(NEGATIVE_INFINITY, false); + assertZero(POSITIVE_INFINITY, false); + assertZero(UNIT, false); + } + + @SmallTest + public void testValueConversions() { + // Unit, simple case + assertValueEquals(UNIT, 1.0f); + assertValueEquals(UNIT, 1.0); + assertValueEquals(UNIT, 1L); + assertValueEquals(UNIT, 1); + assertValueEquals(UNIT, (short)1); + + // Zero, simple case + assertValueEquals(ZERO, 0.0f); + assertValueEquals(ZERO, 0.0); + assertValueEquals(ZERO, 0L); + assertValueEquals(ZERO, 0); + assertValueEquals(ZERO, (short)0); + + // NaN is 0 for integers, not-a-number for floating point + assertValueEquals(NaN, Float.NaN); + assertValueEquals(NaN, Double.NaN); + assertValueEquals(NaN, 0L); + assertValueEquals(NaN, 0); + assertValueEquals(NaN, (short)0); + + // Positive infinity, saturates upwards for integers + assertValueEquals(POSITIVE_INFINITY, Float.POSITIVE_INFINITY); + assertValueEquals(POSITIVE_INFINITY, Double.POSITIVE_INFINITY); + assertValueEquals(POSITIVE_INFINITY, Long.MAX_VALUE); + assertValueEquals(POSITIVE_INFINITY, Integer.MAX_VALUE); + assertValueEquals(POSITIVE_INFINITY, (short)-1); + + // Negative infinity, saturates downwards for integers + assertValueEquals(NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); + assertValueEquals(NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); + assertValueEquals(NEGATIVE_INFINITY, Long.MIN_VALUE); + assertValueEquals(NEGATIVE_INFINITY, Integer.MIN_VALUE); + assertValueEquals(NEGATIVE_INFINITY, (short)0); + + // Normal finite values, round down for integers + final Rational oneQuarter = new Rational(1, 4); + assertValueEquals(oneQuarter, 1.0f / 4.0f); + assertValueEquals(oneQuarter, 1.0 / 4.0); + assertValueEquals(oneQuarter, 0L); + assertValueEquals(oneQuarter, 0); + assertValueEquals(oneQuarter, (short)0); + + final Rational nineFifths = new Rational(9, 5); + assertValueEquals(nineFifths, 9.0f / 5.0f); + assertValueEquals(nineFifths, 9.0 / 5.0); + assertValueEquals(nineFifths, 1L); + assertValueEquals(nineFifths, 1); + assertValueEquals(nineFifths, (short)1); + + final Rational negativeHundred = new Rational(-1000, 10); + assertValueEquals(negativeHundred, -100.f / 1.f); + assertValueEquals(negativeHundred, -100.0 / 1.0); + assertValueEquals(negativeHundred, -100L); + assertValueEquals(negativeHundred, -100); + assertValueEquals(negativeHundred, (short)-100); + + // Short truncates if the result is too large + assertValueEquals(new Rational(Integer.MAX_VALUE, 1), (short)Integer.MAX_VALUE); + assertValueEquals(new Rational(0x00FFFFFF, 1), (short)0x00FFFFFF); + assertValueEquals(new Rational(0x00FF00FF, 1), (short)0x00FF00FF); + } + + @SmallTest + public void testSerialize() throws ClassNotFoundException, IOException { + /* + * Check correct [de]serialization + */ + assertEqualsAfterSerializing(ZERO); + assertEqualsAfterSerializing(NaN); + assertEqualsAfterSerializing(NEGATIVE_INFINITY); + assertEqualsAfterSerializing(POSITIVE_INFINITY); + assertEqualsAfterSerializing(UNIT); + assertEqualsAfterSerializing(new Rational(100, 200)); + assertEqualsAfterSerializing(new Rational(-100, 200)); + assertEqualsAfterSerializing(new Rational(5, 1)); + assertEqualsAfterSerializing(new Rational(Integer.MAX_VALUE, Integer.MIN_VALUE)); + + /* + * Check bad deserialization fails + */ + try { + Rational badZero = createIllegalRational(0, 100); // [0, 100] , should be [0, 1] + Rational results = serializeRoundTrip(badZero); + fail("Deserializing " + results + " should not have succeeded"); + } catch (InvalidObjectException e) { + // OK + } + + try { + Rational badPosInfinity = createIllegalRational(100, 0); // [100, 0] , should be [1, 0] + Rational results = serializeRoundTrip(badPosInfinity); + fail("Deserializing " + results + " should not have succeeded"); + } catch (InvalidObjectException e) { + // OK + } + + try { + Rational badNegInfinity = + createIllegalRational(-100, 0); // [-100, 0] , should be [-1, 0] + Rational results = serializeRoundTrip(badNegInfinity); + fail("Deserializing " + results + " should not have succeeded"); + } catch (InvalidObjectException e) { + // OK + } + + try { + Rational badReduced = createIllegalRational(2, 4); // [2,4] , should be [1, 2] + Rational results = serializeRoundTrip(badReduced); + fail("Deserializing " + results + " should not have succeeded"); + } catch (InvalidObjectException e) { + // OK + } + + try { + Rational badReducedNeg = createIllegalRational(-2, 4); // [-2, 4] should be [-1, 2] + Rational results = serializeRoundTrip(badReducedNeg); + fail("Deserializing " + results + " should not have succeeded"); + } catch (InvalidObjectException e) { + // OK + } + } + + private static void assertValueEquals(Rational object, float expected) { + assertEquals("Checking floatValue() for " + object + ";", + expected, object.floatValue()); + } + + private static void assertValueEquals(Rational object, double expected) { + assertEquals("Checking doubleValue() for " + object + ";", + expected, object.doubleValue()); + } + + private static void assertValueEquals(Rational object, long expected) { + assertEquals("Checking longValue() for " + object + ";", + expected, object.longValue()); + } + + private static void assertValueEquals(Rational object, int expected) { + assertEquals("Checking intValue() for " + object + ";", + expected, object.intValue()); + } + + private static void assertValueEquals(Rational object, short expected) { + assertEquals("Checking shortValue() for " + object + ";", + expected, object.shortValue()); + } + + private static void assertFinite(Rational object, boolean expected) { + assertAction("finite", object, expected, object.isFinite()); + } + + private static void assertInfinite(Rational object, boolean expected) { + assertAction("infinite", object, expected, object.isInfinite()); + } + + private static void assertNaN(Rational object, boolean expected) { + assertAction("NaN", object, expected, object.isNaN()); + } + + private static void assertZero(Rational object, boolean expected) { + assertAction("zero", object, expected, object.isZero()); + } + + private static