blob: 84b2ececbe646c8df988f13dae02351a64ef5e02 [file] [log] [blame]
/*
* 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 android.util.cts;
import static android.util.Rational.NEGATIVE_INFINITY;
import static android.util.Rational.NaN;
import static android.util.Rational.POSITIVE_INFINITY;
import static android.util.Rational.ZERO;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.util.Rational;
import org.junit.Test;
import org.junit.runner.RunWith;
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;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RationalTest {
/** (1,1) */
private static final Rational UNIT = new Rational(1, 1);
@Test
public void testConstructor() {
// Simple case
Rational r = new Rational(1, 2);
assertEquals(1, r.getNumerator());
assertEquals(2, r.getDenominator());
// Denominator negative
r = new Rational(-1, 2);
assertEquals(-1, r.getNumerator());
assertEquals(2, r.getDenominator());
// Numerator negative
r = new Rational(1, -2);
assertEquals(-1, r.getNumerator());
assertEquals(2, r.getDenominator());
// Both negative
r = new Rational(-1, -2);
assertEquals(1, r.getNumerator());
assertEquals(2, r.getDenominator());
// Infinity.
r = new Rational(1, 0);
assertEquals(1, r.getNumerator());
assertEquals(0, r.getDenominator());
// Negative infinity.
r = new Rational(-1, 0);
assertEquals(-1, r.getNumerator());
assertEquals(0, r.getDenominator());
// NaN.
r = new Rational(0, 0);
assertEquals(0, r.getNumerator());
assertEquals(0, r.getDenominator());
}
@Test
public void testEquals() {
Rational r = new Rational(1, 2);
assertEquals(1, r.getNumerator());
assertEquals(2, r.getDenominator());
assertEquals(r, r);
assertFalse(r.equals(null));
assertFalse(r.equals(new Object()));
Rational twoThirds = new Rational(2, 3);
assertFalse(r.equals(twoThirds));
assertFalse(twoThirds.equals(r));
Rational fourSixths = new Rational(4, 6);
assertEquals(twoThirds, fourSixths);
assertEquals(fourSixths, twoThirds);
Rational moreComplicated = new Rational(5*6*7*8*9, 1*2*3*4*5);
Rational moreComplicated2 = new Rational(5*6*7*8*9*78, 1*2*3*4*5*78);
assertEquals(moreComplicated, moreComplicated2);
assertEquals(moreComplicated2, moreComplicated);
// Ensure negatives are fine
twoThirds = new Rational(-2, 3);
fourSixths = new Rational(-4, 6);
assertEquals(twoThirds, fourSixths);
assertEquals(fourSixths, twoThirds);
moreComplicated = new Rational(-5*6*7*8*9, 1*2*3*4*5);
moreComplicated2 = new Rational(-5*6*7*8*9*78, 1*2*3*4*5*78);
assertEquals(moreComplicated, moreComplicated2);
assertEquals(moreComplicated2, moreComplicated);
// 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));
assertTrue(nan2.equals(nan));
assertFalse(nan.equals(r));
assertFalse(r.equals(nan));
// Infinities of the same sign are equal.
Rational posInf = POSITIVE_INFINITY;
Rational posInf2 = new Rational(2, 0);
Rational negInf = NEGATIVE_INFINITY;
Rational negInf2 = new Rational(-2, 0);
assertEquals(posInf, posInf);
assertEquals(negInf, negInf);
assertEquals(posInf, posInf2);
assertEquals(negInf, negInf2);
// Infinities aren't equal to anything else.
assertFalse(posInf.equals(negInf));
assertFalse(negInf.equals(posInf));
assertFalse(negInf.equals(r));
assertFalse(posInf.equals(r));
assertFalse(r.equals(negInf));
assertFalse(r.equals(posInf));
assertFalse(posInf.equals(nan));
assertFalse(negInf.equals(nan));
assertFalse(nan.equals(posInf));
assertFalse(nan.equals(negInf));
}
@Test
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());
}
@Test
public void testCompareTo() {
// unit is equal to itself
verifyCompareEquals(UNIT, new Rational(1, 1));
// NaN is greater than anything but NaN
verifyCompareEquals(NaN, new Rational(0, 0));
verifyGreaterThan(NaN, UNIT);
verifyGreaterThan(NaN, POSITIVE_INFINITY);
verifyGreaterThan(NaN, NEGATIVE_INFINITY);
verifyGreaterThan(NaN, ZERO);
// Positive infinity is greater than any other non-NaN
verifyCompareEquals(POSITIVE_INFINITY, new Rational(1, 0));
verifyGreaterThan(POSITIVE_INFINITY, UNIT);
verifyGreaterThan(POSITIVE_INFINITY, NEGATIVE_INFINITY);
verifyGreaterThan(POSITIVE_INFINITY, ZERO);
// Negative infinity is smaller than any other non-NaN
verifyCompareEquals(NEGATIVE_INFINITY, new Rational(-1, 0));
verifyLessThan(NEGATIVE_INFINITY, UNIT);
verifyLessThan(NEGATIVE_INFINITY, POSITIVE_INFINITY);
verifyLessThan(NEGATIVE_INFINITY, ZERO);
// A finite number with the same denominator is trivially comparable
verifyGreaterThan(new Rational(3, 100), new Rational(1, 100));
verifyGreaterThan(new Rational(3, 100), ZERO);
// Compare finite numbers with different divisors
verifyGreaterThan(new Rational(5, 25), new Rational(1, 10));
verifyGreaterThan(new Rational(5, 25), ZERO);
// Compare finite numbers with different signs
verifyGreaterThan(new Rational(5, 25), new Rational(-1, 10));
verifyLessThan(new Rational(-5, 25), ZERO);
}
@Test
public void testConvenienceMethods() {
// isFinite
verifyFinite(ZERO, true);
verifyFinite(NaN, false);
verifyFinite(NEGATIVE_INFINITY, false);
verifyFinite(POSITIVE_INFINITY, false);
verifyFinite(UNIT, true);
// isInfinite
verifyInfinite(ZERO, false);
verifyInfinite(NaN, false);
verifyInfinite(NEGATIVE_INFINITY, true);
verifyInfinite(POSITIVE_INFINITY, true);
verifyInfinite(UNIT, false);
// isNaN
verifyNaN(ZERO, false);
verifyNaN(NaN, true);
verifyNaN(NEGATIVE_INFINITY, false);
verifyNaN(POSITIVE_INFINITY, false);
verifyNaN(UNIT, false);
// isZero
verifyZero(ZERO, true);
verifyZero(NaN, false);
verifyZero(NEGATIVE_INFINITY, false);
verifyZero(POSITIVE_INFINITY, false);
verifyZero(UNIT, false);
}
@Test
public void testValueConversions() {
// Unit, simple case
verifyValueEquals(UNIT, 1.0f);
verifyValueEquals(UNIT, 1.0);
verifyValueEquals(UNIT, 1L);
verifyValueEquals(UNIT, 1);
verifyValueEquals(UNIT, (short)1);
// Zero, simple case
verifyValueEquals(ZERO, 0.0f);
verifyValueEquals(ZERO, 0.0);
verifyValueEquals(ZERO, 0L);
verifyValueEquals(ZERO, 0);
verifyValueEquals(ZERO, (short)0);
// NaN is 0 for integers, not-a-number for floating point
verifyValueEquals(NaN, Float.NaN);
verifyValueEquals(NaN, Double.NaN);
verifyValueEquals(NaN, 0L);
verifyValueEquals(NaN, 0);
verifyValueEquals(NaN, (short)0);
// Positive infinity, saturates upwards for integers
verifyValueEquals(POSITIVE_INFINITY, Float.POSITIVE_INFINITY);
verifyValueEquals(POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
verifyValueEquals(POSITIVE_INFINITY, Long.MAX_VALUE);
verifyValueEquals(POSITIVE_INFINITY, Integer.MAX_VALUE);
verifyValueEquals(POSITIVE_INFINITY, (short)-1);
// Negative infinity, saturates downwards for integers
verifyValueEquals(NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
verifyValueEquals(NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
verifyValueEquals(NEGATIVE_INFINITY, Long.MIN_VALUE);
verifyValueEquals(NEGATIVE_INFINITY, Integer.MIN_VALUE);
verifyValueEquals(NEGATIVE_INFINITY, (short)0);
// Normal finite values, round down for integers
final Rational oneQuarter = new Rational(1, 4);
verifyValueEquals(oneQuarter, 1.0f / 4.0f);
verifyValueEquals(oneQuarter, 1.0 / 4.0);
verifyValueEquals(oneQuarter, 0L);
verifyValueEquals(oneQuarter, 0);
verifyValueEquals(oneQuarter, (short)0);
final Rational nineFifths = new Rational(9, 5);
verifyValueEquals(nineFifths, 9.0f / 5.0f);
verifyValueEquals(nineFifths, 9.0 / 5.0);
verifyValueEquals(nineFifths, 1L);
verifyValueEquals(nineFifths, 1);
verifyValueEquals(nineFifths, (short)1);
final Rational negativeHundred = new Rational(-1000, 10);
verifyValueEquals(negativeHundred, -100.f / 1.f);
verifyValueEquals(negativeHundred, -100.0 / 1.0);
verifyValueEquals(negativeHundred, -100L);
verifyValueEquals(negativeHundred, -100);
verifyValueEquals(negativeHundred, (short)-100);
// Short truncates if the result is too large
verifyValueEquals(new Rational(Integer.MAX_VALUE, 1), (short)Integer.MAX_VALUE);
verifyValueEquals(new Rational(0x00FFFFFF, 1), (short)0x00FFFFFF);
verifyValueEquals(new Rational(0x00FF00FF, 1), (short)0x00FF00FF);
}
@Test
public void testSerialize() throws ClassNotFoundException, IOException {
/*
* Check correct [de]serialization
*/
verifyEqualsAfterSerializing(ZERO);
verifyEqualsAfterSerializing(NaN);
verifyEqualsAfterSerializing(NEGATIVE_INFINITY);
verifyEqualsAfterSerializing(POSITIVE_INFINITY);
verifyEqualsAfterSerializing(UNIT);
verifyEqualsAfterSerializing(new Rational(100, 200));
verifyEqualsAfterSerializing(new Rational(-100, 200));
verifyEqualsAfterSerializing(new Rational(5, 1));
verifyEqualsAfterSerializing(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
}
}
@Test
public void testParseRational() {
assertEquals(new Rational(1, 2), Rational.parseRational("3:+6"));
assertEquals(new Rational(1, 2), Rational.parseRational("-3:-6"));
assertEquals(Rational.NaN, Rational.parseRational("NaN"));
assertEquals(Rational.POSITIVE_INFINITY, Rational.parseRational("Infinity"));
assertEquals(Rational.NEGATIVE_INFINITY, Rational.parseRational("-Infinity"));
assertEquals(Rational.ZERO, Rational.parseRational("0/261"));
assertEquals(Rational.NaN, Rational.parseRational("0/-0"));
assertEquals(Rational.POSITIVE_INFINITY, Rational.parseRational("1000/+0"));
assertEquals(Rational.NEGATIVE_INFINITY, Rational.parseRational("-1000/-0"));
Rational r = new Rational(10, 15);
assertEquals(r, Rational.parseRational(r.toString()));
}
@Test(expected=NumberFormatException.class)
public void testParseRationalInvalid1() {
Rational.parseRational("1.5");
}
@Test(expected=NumberFormatException.class)
public void testParseRationalInvalid2() {
Rational.parseRational("239");
}
private static void verifyValueEquals(Rational object, float expected) {
assertEquals("Checking floatValue() for " + object + ";",
expected, object.floatValue(), 0.0f);
}
private static void verifyValueEquals(Rational object, double expected) {
assertEquals("Checking doubleValue() for " + object + ";",
expected, object.doubleValue(), 0.0f);
}
private static void verifyValueEquals(Rational object, long expected) {
assertEquals("Checking longValue() for " + object + ";",
expected, object.longValue());
}
private static void verifyValueEquals(Rational object, int expected) {
assertEquals("Checking intValue() for " + object + ";",
expected, object.intValue());
}
private static void verifyValueEquals(Rational object, short expected) {
assertEquals("Checking shortValue() for " + object + ";",
expected, object.shortValue());
}
private static void verifyFinite(Rational object, boolean expected) {
verifyAction("finite", object, expected, object.isFinite());
}
private static void verifyInfinite(Rational object, boolean expected) {
verifyAction("infinite", object, expected, object.isInfinite());
}
private static void verifyNaN(Rational object, boolean expected) {
verifyAction("NaN", object, expected, object.isNaN());
}
private static void verifyZero(Rational object, boolean expected) {
verifyAction("zero", object, expected, object.isZero());
}
private static <T> void verifyAction(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 verifyLessThan(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 verifyGreaterThan(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 verifyCompareEquals(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 verifyEqualsAfterSerializing(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);
}
}
}