diff options
author | Igor Murashkin <iam@google.com> | 2014-06-05 18:02:22 -0700 |
---|---|---|
committer | Igor Murashkin <iam@google.com> | 2014-06-06 16:38:09 -0700 |
commit | 007bfb14d2d720cdd699cfbb36ce206246901cef (patch) | |
tree | cb7838e0d1417e554566040b0284b53148b5c23e | |
parent | 21547d66a9ce591ff30a3ad4102f7f30a4764d80 (diff) | |
download | frameworks_base-007bfb14d2d720cdd699cfbb36ce206246901cef.zip frameworks_base-007bfb14d2d720cdd699cfbb36ce206246901cef.tar.gz frameworks_base-007bfb14d2d720cdd699cfbb36ce206246901cef.tar.bz2 |
util: Make Rational a Number/Comparable; add Range#inRange
* Also changes Rational to reduce the numerator/denominator by
its greatest common divisor at construction time (e.g. (2/4 -> 1/2)).
Bug: 15432042
Change-Id: Ib827abccf44a040667e5931cf9442afc86b57e2d
-rw-r--r-- | api/current.txt | 16 | ||||
-rw-r--r-- | core/java/android/util/Range.java | 30 | ||||
-rw-r--r-- | core/java/android/util/Rational.java | 457 | ||||
-rw-r--r-- | media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RangeTest.java | 175 | ||||
-rw-r--r-- | media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java | 406 |
5 files changed, 995 insertions, 89 deletions
diff --git a/api/current.txt b/api/current.txt index 5aca2ba..8d3cdef 100644 --- a/api/current.txt +++ b/api/current.txt @@ -30533,12 +30533,26 @@ package android.util { method public static android.util.Range<T> create(T, T); method public T getLower(); method public T getUpper(); + method public boolean inRange(T); } - public final class Rational { + public final class Rational extends java.lang.Number implements java.lang.Comparable { ctor public Rational(int, int); + method public int compareTo(android.util.Rational); + method public double doubleValue(); + method public float floatValue(); method public int getDenominator(); method public int getNumerator(); + method public int intValue(); + method public boolean isFinite(); + method public boolean isInfinite(); + method public boolean isNaN(); + method public boolean isZero(); + method public long longValue(); + field public static final android.util.Rational NEGATIVE_INFINITY; + field public static final android.util.Rational NaN; + field public static final android.util.Rational POSITIVE_INFINITY; + field public static final android.util.Rational ZERO; } public final class Size { diff --git a/core/java/android/util/Range.java b/core/java/android/util/Range.java index d7e8cf0..3907e77 100644 --- a/core/java/android/util/Range.java +++ b/core/java/android/util/Range.java @@ -97,6 +97,27 @@ public final class Range<T extends Comparable<? super T>> { } /** + * Checks if the {@code value} is within the bounds of this range. + * + * <p>A value is considered to be within this range if it's {@code >=} then + * the lower endpoint <i>and</i> {@code <=} to the upper endpoint (using the {@link Comparable} + * interface.</p> + * + * @param value a non-{@code null} {@code T} reference + * @return {@code true} if the value is within this inclusive range, {@code false} otherwise + * + * @throws NullPointerException if {@code value} was {@code null} + */ + public boolean inRange(T value) { + checkNotNull(value, "value must not be null"); + + boolean gteLower = value.compareTo(mLower) >= 0; + boolean lteUpper = value.compareTo(mUpper) <= 0; + + return gteLower && lteUpper; + } + + /** * Compare two ranges for equality. * * <p>A range is considered equal if and only if both the lower and upper endpoints @@ -105,16 +126,13 @@ public final class Range<T extends Comparable<? super T>> { * @return {@code true} if the ranges are equal, {@code false} otherwise */ @Override - public boolean equals(final Object obj) { + public boolean equals(Object obj) { if (obj == null) { return false; - } - if (this == obj) { + } else if (this == obj) { return true; - } - if (obj instanceof Range) { + } else if (obj instanceof Range) { @SuppressWarnings("rawtypes") - final Range other = (Range) obj; return mLower.equals(other.mLower) && mUpper.equals(other.mUpper); } diff --git a/core/java/android/util/Rational.java b/core/java/android/util/Rational.java index 8d4c67f..9952859 100644 --- a/core/java/android/util/Rational.java +++ b/core/java/android/util/Rational.java @@ -15,29 +15,88 @@ */ package android.util; +import static com.android.internal.util.Preconditions.*; + +import java.io.IOException; +import java.io.InvalidObjectException; + /** * <p>An immutable data type representation a rational number.</p> * * <p>Contains a pair of {@code int}s representing the numerator and denominator of a * Rational number. </p> */ -public final class Rational { +public final class Rational extends Number implements Comparable<Rational> { + /** + * Constant for the <em>Not-a-Number (NaN)</em> value of the {@code Rational} type. + * + * <p>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}).</p> + * + * <p>Equivalent to constructing a new rational with both the numerator and denominator + * equal to {@code 0}.</p> + */ + public static final Rational NaN = new Rational(0, 0); + + /** + * Constant for the positive infinity value of the {@code Rational} type. + * + * <p>Equivalent to constructing a new rational with a positive numerator and a denominator + * equal to {@code 0}.</p> + */ + public static final Rational POSITIVE_INFINITY = new Rational(1, 0); + + /** + * Constant for the negative infinity value of the {@code Rational} type. + * + * <p>Equivalent to constructing a new rational with a negative numerator and a denominator + * equal to {@code 0}.</p> + */ + public static final Rational NEGATIVE_INFINITY = new Rational(-1, 0); + + /** + * Constant for the zero value of the {@code Rational} type. + * + * <p>Equivalent to constructing a new rational with a numerator equal to {@code 0} and + * any non-zero denominator.</p> + */ + public static final Rational ZERO = new Rational(0, 1); + + /** + * Unique version number per class to be compliant with {@link java.io.Serializable}. + * + * <p>Increment each time the fields change in any way.</p> + */ + private static final long serialVersionUID = 1L; + + /* + * Do not change the order of these fields or add new instance fields to maintain the + * Serializable compatibility across API revisions. + */ private final int mNumerator; private final int mDenominator; /** - * <p>Create a Rational with a given numerator and denominator.</p> + * <p>Create a {@code Rational} with a given numerator and denominator.</p> * * <p>The signs of the numerator and the denominator may be flipped such that the denominator - * is always positive.</p> + * is always positive. Both the numerator and denominator will be converted to their reduced + * forms (see {@link #equals} for more details).</p> * - * <p>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.</p> + * <p>For example, + * <ul> + * <li>a rational of {@code 2/4} will be reduced to {@code 1/2}. + * <li>a rational of {@code 1/-1} will be flipped to {@code -1/1} + * <li>a rational of {@code 5/0} will be reduced to {@code 1/0} + * <li>a rational of {@code 0/5} will be reduced to {@code 0/1} + * </ul> + * </p> * * @param numerator the numerator of the rational * @param denominator the denominator of the rational + * + * @see #equals */ public Rational(int numerator, int denominator) { @@ -46,32 +105,100 @@ public final class Rational { denominator = -denominator; } - mNumerator = numerator; - mDenominator = denominator; + // Convert to reduced form + if (denominator == 0 && numerator > 0) { + mNumerator = 1; // +Inf + mDenominator = 0; + } else if (denominator == 0 && numerator < 0) { + mNumerator = -1; // -Inf + mDenominator = 0; + } else if (denominator == 0 && numerator == 0) { + mNumerator = 0; // NaN + mDenominator = 0; + } else if (numerator == 0) { + mNumerator = 0; + mDenominator = 1; + } else { + int gcd = gcd(numerator, denominator); + + mNumerator = numerator / gcd; + mDenominator = denominator / gcd; + } } /** * Gets the numerator of the rational. + * + * <p>The numerator will always return {@code 1} if this rational represents + * infinity (that is, the denominator is {@code 0}).</p> */ public int getNumerator() { - if (mDenominator == 0) { - return 0; - } return mNumerator; } /** * Gets the denominator of the rational + * + * <p>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}).</p> + * + * <p>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 <em>Not-a-Number (NaN)</em> value. + * + * <p>A {@code NaN} value occurs when both the numerator and the denominator are {@code 0}.</p> + * + * @return {@code true} if this rational is a <em>Not-a-Number (NaN)</em> 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. + * + * <p>An infinite value occurs when the denominator is {@code 0} (but the numerator is not).</p> + * + * @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. + * + * <p>A finite value occurs when the denominator is not {@code 0}; in other words + * the rational is neither infinity or {@code NaN}.</p> + * + * @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. + * + * <p>A zero value is a {@link #isFinite finite} rational with a numerator of {@code 0}.</p> + * + * @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 { /** * <p>Compare this Rational to another object and see if they are equal.</p> * - * <p>A Rational object can only be equal to another Rational object (comparing against any other - * type will return false).</p> + * <p>A Rational object can only be equal to another Rational object (comparing against any + * other type will return {@code false}).</p> * * <p>A Rational object is considered equal to another Rational object if and only if one of - * the following holds</p>: - * <ul><li>Both are NaN</li> + * the following holds:</p> + * <ul><li>Both are {@code NaN}</li> * <li>Both are infinities of the same sign</li> * <li>Both have the same numerator and denominator in their reduced form</li> * </ul> @@ -96,12 +223,12 @@ public final class Rational { * denominator by their greatest common divisor.</p> * * <pre>{@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 * }</pre> * * @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"}. + * + * <p>The following rules of conversion apply: + * <ul> + * <li>{@code NaN} values will return {@code "NaN"} + * <li>Positive infinity values will return {@code "Infinity"} + * <li>Negative infinity values will return {@code "-Infinity"} + * <li>All other values will return {@code "numerator/denominator"} where {@code numerator} + * and {@code denominator} are substituted with the appropriate numerator and denominator + * values. + * </ul></p> + */ @Override public String toString() { if (isNaN()) { return "NaN"; - } else if (isInf()) { + } else if (isPosInf()) { return "Infinity"; } else if (isNegInf()) { return "-Infinity"; @@ -160,7 +277,8 @@ public final class Rational { * @hide */ public float toFloat() { - return (float) mNumerator / (float) mDenominator; + // TODO: remove this duplicate function (used in CTS and the shim) + return floatValue(); } /** @@ -177,20 +295,24 @@ public final class Rational { /** * Calculates the greatest common divisor using Euclid's algorithm. * + * <p><em>Visible for testing only.</em></p> + * + * @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}. + * + * <p>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.</p> + * + * @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}. + * + * <p>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.</p> + * + * @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}. + * + * <p>{@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: + * + * <p> + * <ul> + * <li>Positive infinity saturates to the largest maximum integer + * {@link Integer#MAX_VALUE}</li> + * <li>Negative infinity saturates to the smallest maximum integer + * {@link Integer#MIN_VALUE}</li> + * <li><em>Not-A-Number (NaN)</em> returns {@code 0}.</li> + * </ul> + * </p> + * + * @return the divided value of the numerator and denominator as a {@code int}. + */ + @Override + public int intValue() { + // Mimic float to int conversion rules from JLS 5.1.3 + + if (isPosInf()) { + return Integer.MAX_VALUE; + } else if (isNegInf()) { + return Integer.MIN_VALUE; + } else if (isNaN()) { + return 0; + } else { // finite + return mNumerator / mDenominator; + } + } + + /** + * Returns the value of the specified number as a {@code long}. + * + * <p>{@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: + * + * <p> + * <ul> + * <li>Positive infinity saturates to the largest maximum long + * {@link Long#MAX_VALUE}</li> + * <li>Negative infinity saturates to the smallest maximum long + * {@link Long#MIN_VALUE}</li> + * <li><em>Not-A-Number (NaN)</em> returns {@code 0}.</li> + * </ul> + * </p> + * + * @return the divided value of the numerator and denominator as a {@code long}. + */ + @Override + public long longValue() { + // Mimic float to long conversion rules from JLS 5.1.3 + + if (isPosInf()) { + return Long.MAX_VALUE; + } else if (isNegInf()) { + return Long.MIN_VALUE; + } else if (isNaN()) { + return 0; + } else { // finite + return mNumerator / mDenominator; + } + } + + /** + * Returns the value of the specified number as a {@code short}. + * + * <p>{@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.</p> + * + * @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. + * + * <p>{@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:</p> + * + * <ul> + * <li>Positive infinity is greater than any other finite number (or negative infinity) + * <li>Negative infinity is less than any other finite number (or positive infinity) + * <li>The finite number represented by this rational is checked numerically + * against the other finite number by converting both rationals to a common denominator multiple + * and comparing their numerators. + * </ul> + * + * @param another the rational to be compared + * + * @return a negative integer, zero, or a positive integer as this object is less than, + * equal to, or greater than the specified rational. + * + * @throws NullPointerException if {@code another} was {@code null} + */ + @Override + public int compareTo(Rational another) { + checkNotNull(another, "another must not be null"); + + if (equals(another)) { + return 0; + } else if (isNaN()) { // NaN is greater than the other non-NaN value + return 1; + } else if (another.isNaN()) { // the other NaN is greater than this non-NaN value + return -1; + } else if (isPosInf() || another.isNegInf()) { + return 1; // positive infinity is greater than any non-NaN/non-posInf value + } else if (isNegInf() || another.isPosInf()) { + return -1; // negative infinity is less than any non-NaN/non-negInf value + } + + // else both this and another are finite numbers + + // make the denominators the same, then compare numerators + long thisNumerator = ((long)mNumerator) * another.mDenominator; // long to avoid overflow + long otherNumerator = ((long)another.mNumerator) * mDenominator; // long to avoid overflow + + // avoid underflow from subtraction by doing comparisons + if (thisNumerator < otherNumerator) { + return -1; + } else if (thisNumerator > otherNumerator) { + return 1; + } else { + // This should be covered by #equals, but have this code path just in case + return 0; + } + } + + /* + * Serializable implementation. + * + * The following methods are omitted: + * >> writeObject - the default is sufficient (field by field serialization) + * >> readObjectNoData - the default is sufficient (0s for both fields is a NaN) + */ + + /** + * writeObject with default serialized form - guards against + * deserializing non-reduced forms of the rational. + * + * @throws InvalidObjectException if the invariants were violated + */ + private void readObject(java.io.ObjectInputStream in) + throws IOException, ClassNotFoundException { + in.defaultReadObject(); + + /* + * Guard against trying to deserialize illegal values (in this case, ones + * that don't have a standard reduced form). + * + * - Non-finite values must be one of [0, 1], [0, 0], [0, 1], [0, -1] + * - Finite values must always have their greatest common divisor as 1 + */ + + if (mNumerator == 0) { // either zero or NaN + if (mDenominator == 1 || mDenominator == 0) { + return; + } + throw new InvalidObjectException( + "Rational must be deserialized from a reduced form for zero values"); + } else if (mDenominator == 0) { // either positive or negative infinity + if (mNumerator == 1 || mNumerator == -1) { + return; + } + throw new InvalidObjectException( + "Rational must be deserialized from a reduced form for infinity values"); + } else { // finite value + if (gcd(mNumerator, mDenominator) > 1) { + throw new InvalidObjectException( + "Rational must be deserialized from a reduced form for finite values"); + } + } + } } diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RangeTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RangeTest.java new file mode 100644 index 0000000..d90a4bc --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RangeTest.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.mediaframeworktest.unit; + +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Range; +import android.util.Rational; + +/** + * <pre> + * adb shell am instrument \ + * -e class 'com.android.mediaframeworktest.unit.RangeTest' \ + * -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner + * </pre> + */ +public class RangeTest extends junit.framework.TestCase { + + @SmallTest + public void testConstructor() { + // Trivial, same range + Range<Integer> intRange = new Range<Integer>(1, 1); + + assertLower(intRange, 1); + assertUpper(intRange, 1); + + // Different values in range + Range<Integer> intRange2 = new Range<Integer>(100, 200); + assertLower(intRange2, 100); + assertUpper(intRange2, 200); + + Range<Float> floatRange = new Range<Float>(Float.NEGATIVE_INFINITY, + Float.POSITIVE_INFINITY); + assertLower(floatRange, Float.NEGATIVE_INFINITY); + assertUpper(floatRange, Float.POSITIVE_INFINITY); + } + + @SmallTest + public void testIllegalValues() { + // Test NPEs + try { + new Range<Integer>(null, null); + fail("Expected exception to be thrown for (null, null)"); + } catch (NullPointerException e) { + // OK: both args are null + } + + try { + new Range<Integer>(null, 0); + fail("Expected exception to be thrown for (null, 0)"); + } catch (NullPointerException e) { + // OK: left arg is null + } + + try { + new Range<Integer>(0, null); + fail("Expected exception to be thrown for (0, null)"); + } catch (NullPointerException e) { + // OK: right arg is null + } + + // Test IAEs + + try { + new Range<Integer>(50, -50); + fail("Expected exception to be thrown for (50, -50)"); + } catch (IllegalArgumentException e) { + // OK: 50 > -50 so it fails + } + + try { + new Range<Float>(0.0f, Float.NEGATIVE_INFINITY); + fail("Expected exception to be thrown for (0.0f, -Infinity)"); + } catch (IllegalArgumentException e) { + // OK: 0.0f is > NEGATIVE_INFINITY, so it fails + } + } + + @SmallTest + public void testEquals() { + Range<Float> oneHalf = Range.create(1.0f, 2.0f); + Range<Float> oneHalf2 = new Range<Float>(1.0f, 2.0f); + assertEquals(oneHalf, oneHalf2); + assertHashCodeEquals(oneHalf, oneHalf2); + + Range<Float> twoThirds = new Range<Float>(2.0f, 3.0f); + Range<Float> twoThirds2 = Range.create(2.0f, 3.0f); + assertEquals(twoThirds, twoThirds2); + assertHashCodeEquals(twoThirds, twoThirds2); + + Range<Rational> negativeOneTenthPositiveOneTenth = + new Range<Rational>(new Rational(-1, 10), new Rational(1, 10)); + Range<Rational> negativeOneTenthPositiveOneTenth2 = + Range.create(new Rational(-1, 10), new Rational(1, 10)); + assertEquals(negativeOneTenthPositiveOneTenth, negativeOneTenthPositiveOneTenth2); + assertHashCodeEquals(negativeOneTenthPositiveOneTenth, negativeOneTenthPositiveOneTenth2); + } + + @SmallTest + public void testInRange() { + Range<Integer> hundredOneTwo = Range.create(100, 200); + + assertInRange(hundredOneTwo, 100); + assertInRange(hundredOneTwo, 200); + assertInRange(hundredOneTwo, 150); + assertOutOfRange(hundredOneTwo, 99); + assertOutOfRange(hundredOneTwo, 201); + assertOutOfRange(hundredOneTwo, 100000); + + Range<Float> infinities = Range.create(Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY); + + assertInRange(infinities, Float.NEGATIVE_INFINITY); + assertInRange(infinities, Float.POSITIVE_INFINITY); + assertInRange(infinities, 0.0f); + assertOutOfRange(infinities, Float.NaN); + + Range<Rational> negativeOneTenthPositiveOneTenth = + new Range<Rational>(new Rational(-1, 10), new Rational(1, 10)); + assertInRange(negativeOneTenthPositiveOneTenth, new Rational(-1, 10)); + assertInRange(negativeOneTenthPositiveOneTenth, new Rational(1, 10)); + assertInRange(negativeOneTenthPositiveOneTenth, Rational.ZERO); + assertOutOfRange(negativeOneTenthPositiveOneTenth, new Rational(-100, 1)); + assertOutOfRange(negativeOneTenthPositiveOneTenth, new Rational(100, 1)); + } + + private static <T extends Comparable<? super T>> void assertInRange(Range<T> object, T needle) { + assertAction("in-range", object, needle, true, object.inRange(needle)); + } + + private static <T extends Comparable<? super T>> void assertOutOfRange(Range<T> object, + T needle) { + assertAction("out-of-range", object, needle, false, object.inRange(needle)); + } + + private static <T extends Comparable<? super T>> void assertUpper(Range<T> object, T expected) { + assertAction("upper", object, expected, object.getUpper()); + } + + private static <T extends Comparable<? super T>> void assertLower(Range<T> object, T expected) { + assertAction("lower", object, expected, object.getLower()); + } + + private static <T, T2> void assertAction(String action, T object, T2 expected, + T2 actual) { + assertEquals("Expected " + object + " " + action + " to be ", + expected, actual); + } + + private static <T, T2> void assertAction(String action, T object, T2 needle, boolean expected, + boolean actual) { + String expectedMessage = expected ? action : ("not " + action); + assertEquals("Expected " + needle + " to be " + expectedMessage + " of " + object, + expected, actual); + } + + private static <T extends Comparable<? super T>> void assertHashCodeEquals( + Range<T> left, Range<T> right) { + assertEquals("Left hash code for " + left + + " expected to be equal to right hash code for " + right, + left.hashCode(), right.hashCode()); + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java index 18c0d3e..1bb7db9 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RationalTest.java @@ -19,6 +19,17 @@ package com.android.mediaframeworktest.unit; import android.test.suitebuilder.annotation.SmallTest; import android.util.Rational; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Field; + +import static android.util.Rational.*; + /** * <pre> * adb shell am instrument \ @@ -27,6 +38,22 @@ import android.util.Rational; * </pre> */ 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. @@ -67,24 +94,6 @@ public class RationalTest extends junit.framework.TestCase { } @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); assertEquals(1, r.getNumerator()); @@ -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 <T> void assertAction(String action, T object, boolean expected, + boolean actual) { + String expectedMessage = expected ? action : ("not " + action); + assertEquals("Expected " + object + " to be " + expectedMessage, + expected, actual); + } + + private static <T extends Comparable<? super T>> void assertLessThan(T left, T right) { + assertTrue("Expected (LR) left " + left + " to be less than right " + right, + left.compareTo(right) < 0); + assertTrue("Expected (RL) left " + left + " to be less than right " + right, + right.compareTo(left) > 0); + } + + private static <T extends Comparable<? super T>> void assertGreaterThan(T left, T right) { + assertTrue("Expected (LR) left " + left + " to be greater than right " + right, + left.compareTo(right) > 0); + assertTrue("Expected (RL) left " + left + " to be greater than right " + right, + right.compareTo(left) < 0); + } + + private static <T extends Comparable<? super T>> void assertCompareEquals(T left, T right) { + assertTrue("Expected (LR) left " + left + " to be compareEquals to right " + right, + left.compareTo(right) == 0); + assertTrue("Expected (RL) left " + left + " to be compareEquals to right " + right, + right.compareTo(left) == 0); + } + + private static <T extends Serializable> byte[] serialize(T obj) throws IOException { + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + try (ObjectOutputStream objectStream = new ObjectOutputStream(byteStream)) { + objectStream.writeObject(obj); + } + return byteStream.toByteArray(); + } + + private static <T extends Serializable> T deserialize(byte[] array, Class<T> klass) + throws IOException, ClassNotFoundException { + ByteArrayInputStream bais = new ByteArrayInputStream(array); + ObjectInputStream ois = new ObjectInputStream(bais); + Object obj = ois.readObject(); + return klass.cast(obj); + } + + @SuppressWarnings("unchecked") + private static <T extends Serializable> T serializeRoundTrip(T obj) + throws IOException, ClassNotFoundException { + Class<T> klass = (Class<T>) obj.getClass(); + byte[] arr = serialize(obj); + T serialized = deserialize(arr, klass); + return serialized; + } + + private static <T extends Serializable> void assertEqualsAfterSerializing(T obj) + throws ClassNotFoundException, IOException { + T serialized = serializeRoundTrip(obj); + assertEquals("Expected values to be equal after serialization round-trip", obj, serialized); + } + + private static Rational createIllegalRational(int numerator, int denominator) { + Rational r = new Rational(numerator, denominator); + mutateField(r, "mNumerator", numerator); + mutateField(r, "mDenominator", denominator); + return r; + } + + private static <T> void mutateField(T object, String name, int value) { + try { + Field f = object.getClass().getDeclaredField(name); + f.setAccessible(true); + f.set(object, value); + } catch (NoSuchFieldException e) { + throw new AssertionError(e); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } catch (IllegalArgumentException e) { + throw new AssertionError(e); + } + } } |