1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.util.cts;
18 
19 import static android.util.Rational.NEGATIVE_INFINITY;
20 import static android.util.Rational.NaN;
21 import static android.util.Rational.POSITIVE_INFINITY;
22 import static android.util.Rational.ZERO;
23 
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertFalse;
26 import static org.junit.Assert.assertTrue;
27 import static org.junit.Assert.fail;
28 
29 import android.util.Rational;
30 
31 import androidx.test.filters.SmallTest;
32 import androidx.test.runner.AndroidJUnit4;
33 
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 
37 import java.io.ByteArrayInputStream;
38 import java.io.ByteArrayOutputStream;
39 import java.io.IOException;
40 import java.io.InvalidObjectException;
41 import java.io.ObjectInputStream;
42 import java.io.ObjectOutputStream;
43 import java.io.Serializable;
44 import java.lang.reflect.Field;
45 
46 @SmallTest
47 @RunWith(AndroidJUnit4.class)
48 public class RationalTest {
49 
50     /** (1,1) */
51     private static final Rational UNIT = new Rational(1, 1);
52 
53     @Test
testConstructor()54     public void testConstructor() {
55 
56         // Simple case
57         Rational r = new Rational(1, 2);
58         assertEquals(1, r.getNumerator());
59         assertEquals(2, r.getDenominator());
60 
61         // Denominator negative
62         r = new Rational(-1, 2);
63         assertEquals(-1, r.getNumerator());
64         assertEquals(2, r.getDenominator());
65 
66         // Numerator negative
67         r = new Rational(1, -2);
68         assertEquals(-1, r.getNumerator());
69         assertEquals(2, r.getDenominator());
70 
71         // Both negative
72         r = new Rational(-1, -2);
73         assertEquals(1, r.getNumerator());
74         assertEquals(2, r.getDenominator());
75 
76         // Infinity.
77         r = new Rational(1, 0);
78         assertEquals(1, r.getNumerator());
79         assertEquals(0, r.getDenominator());
80 
81         // Negative infinity.
82         r = new Rational(-1, 0);
83         assertEquals(-1, r.getNumerator());
84         assertEquals(0, r.getDenominator());
85 
86         // NaN.
87         r = new Rational(0, 0);
88         assertEquals(0, r.getNumerator());
89         assertEquals(0, r.getDenominator());
90     }
91 
92     @Test
testEquals()93     public void testEquals() {
94         Rational r = new Rational(1, 2);
95         assertEquals(1, r.getNumerator());
96         assertEquals(2, r.getDenominator());
97 
98         assertEquals(r, r);
99         assertFalse(r.equals(null));
100         assertFalse(r.equals(new Object()));
101 
102         Rational twoThirds = new Rational(2, 3);
103         assertFalse(r.equals(twoThirds));
104         assertFalse(twoThirds.equals(r));
105 
106         Rational fourSixths = new Rational(4, 6);
107         assertEquals(twoThirds, fourSixths);
108         assertEquals(fourSixths, twoThirds);
109 
110         Rational moreComplicated = new Rational(5*6*7*8*9, 1*2*3*4*5);
111         Rational moreComplicated2 = new Rational(5*6*7*8*9*78, 1*2*3*4*5*78);
112         assertEquals(moreComplicated, moreComplicated2);
113         assertEquals(moreComplicated2, moreComplicated);
114 
115         // Ensure negatives are fine
116         twoThirds = new Rational(-2, 3);
117         fourSixths = new Rational(-4, 6);
118         assertEquals(twoThirds, fourSixths);
119         assertEquals(fourSixths, twoThirds);
120 
121         moreComplicated = new Rational(-5*6*7*8*9, 1*2*3*4*5);
122         moreComplicated2 = new Rational(-5*6*7*8*9*78, 1*2*3*4*5*78);
123         assertEquals(moreComplicated, moreComplicated2);
124         assertEquals(moreComplicated2, moreComplicated);
125 
126         // Zero is always equal to itself
127         Rational zero2 = new Rational(0, 100);
128         assertEquals(ZERO, zero2);
129         assertEquals(zero2, ZERO);
130 
131         // NaN is always equal to itself
132         Rational nan = NaN;
133         Rational nan2 = new Rational(0, 0);
134         assertTrue(nan.equals(nan));
135         assertTrue(nan.equals(nan2));
136         assertTrue(nan2.equals(nan));
137         assertFalse(nan.equals(r));
138         assertFalse(r.equals(nan));
139 
140         // Infinities of the same sign are equal.
141         Rational posInf = POSITIVE_INFINITY;
142         Rational posInf2 = new Rational(2, 0);
143         Rational negInf = NEGATIVE_INFINITY;
144         Rational negInf2 = new Rational(-2, 0);
145         assertEquals(posInf, posInf);
146         assertEquals(negInf, negInf);
147         assertEquals(posInf, posInf2);
148         assertEquals(negInf, negInf2);
149 
150         // Infinities aren't equal to anything else.
151         assertFalse(posInf.equals(negInf));
152         assertFalse(negInf.equals(posInf));
153         assertFalse(negInf.equals(r));
154         assertFalse(posInf.equals(r));
155         assertFalse(r.equals(negInf));
156         assertFalse(r.equals(posInf));
157         assertFalse(posInf.equals(nan));
158         assertFalse(negInf.equals(nan));
159         assertFalse(nan.equals(posInf));
160         assertFalse(nan.equals(negInf));
161     }
162 
163     @Test
testReduction()164     public void testReduction() {
165         Rational moreComplicated = new Rational(5 * 78, 7 * 78);
166         assertEquals(new Rational(5, 7), moreComplicated);
167         assertEquals(5, moreComplicated.getNumerator());
168         assertEquals(7, moreComplicated.getDenominator());
169 
170         Rational posInf = new Rational(5, 0);
171         assertEquals(1, posInf.getNumerator());
172         assertEquals(0, posInf.getDenominator());
173         assertEquals(POSITIVE_INFINITY, posInf);
174 
175         Rational negInf = new Rational(-100, 0);
176         assertEquals(-1, negInf.getNumerator());
177         assertEquals(0, negInf.getDenominator());
178         assertEquals(NEGATIVE_INFINITY, negInf);
179 
180         Rational zero = new Rational(0, -100);
181         assertEquals(0, zero.getNumerator());
182         assertEquals(1, zero.getDenominator());
183         assertEquals(ZERO, zero);
184 
185         Rational flipSigns = new Rational(1, -1);
186         assertEquals(-1, flipSigns.getNumerator());
187         assertEquals(1, flipSigns.getDenominator());
188 
189         Rational flipAndReduce = new Rational(100, -200);
190         assertEquals(-1, flipAndReduce.getNumerator());
191         assertEquals(2, flipAndReduce.getDenominator());
192     }
193 
194     @Test
testCompareTo()195     public void testCompareTo() {
196         // unit is equal to itself
197         verifyCompareEquals(UNIT, new Rational(1, 1));
198 
199         // NaN is greater than anything but NaN
200         verifyCompareEquals(NaN, new Rational(0, 0));
201         verifyGreaterThan(NaN, UNIT);
202         verifyGreaterThan(NaN, POSITIVE_INFINITY);
203         verifyGreaterThan(NaN, NEGATIVE_INFINITY);
204         verifyGreaterThan(NaN, ZERO);
205 
206         // Positive infinity is greater than any other non-NaN
207         verifyCompareEquals(POSITIVE_INFINITY, new Rational(1, 0));
208         verifyGreaterThan(POSITIVE_INFINITY, UNIT);
209         verifyGreaterThan(POSITIVE_INFINITY, NEGATIVE_INFINITY);
210         verifyGreaterThan(POSITIVE_INFINITY, ZERO);
211 
212         // Negative infinity is smaller than any other non-NaN
213         verifyCompareEquals(NEGATIVE_INFINITY, new Rational(-1, 0));
214         verifyLessThan(NEGATIVE_INFINITY, UNIT);
215         verifyLessThan(NEGATIVE_INFINITY, POSITIVE_INFINITY);
216         verifyLessThan(NEGATIVE_INFINITY, ZERO);
217 
218         // A finite number with the same denominator is trivially comparable
219         verifyGreaterThan(new Rational(3, 100), new Rational(1, 100));
220         verifyGreaterThan(new Rational(3, 100), ZERO);
221 
222         // Compare finite numbers with different divisors
223         verifyGreaterThan(new Rational(5, 25), new Rational(1, 10));
224         verifyGreaterThan(new Rational(5, 25), ZERO);
225 
226         // Compare finite numbers with different signs
227         verifyGreaterThan(new Rational(5, 25), new Rational(-1, 10));
228         verifyLessThan(new Rational(-5, 25), ZERO);
229     }
230 
231     @Test
testConvenienceMethods()232     public void testConvenienceMethods() {
233         // isFinite
234         verifyFinite(ZERO, true);
235         verifyFinite(NaN, false);
236         verifyFinite(NEGATIVE_INFINITY, false);
237         verifyFinite(POSITIVE_INFINITY, false);
238         verifyFinite(UNIT, true);
239 
240         // isInfinite
241         verifyInfinite(ZERO, false);
242         verifyInfinite(NaN, false);
243         verifyInfinite(NEGATIVE_INFINITY, true);
244         verifyInfinite(POSITIVE_INFINITY, true);
245         verifyInfinite(UNIT, false);
246 
247         // isNaN
248         verifyNaN(ZERO, false);
249         verifyNaN(NaN, true);
250         verifyNaN(NEGATIVE_INFINITY, false);
251         verifyNaN(POSITIVE_INFINITY, false);
252         verifyNaN(UNIT, false);
253 
254         // isZero
255         verifyZero(ZERO, true);
256         verifyZero(NaN, false);
257         verifyZero(NEGATIVE_INFINITY, false);
258         verifyZero(POSITIVE_INFINITY, false);
259         verifyZero(UNIT, false);
260     }
261 
262     @Test
testValueConversions()263     public void testValueConversions() {
264         // Unit, simple case
265         verifyValueEquals(UNIT, 1.0f);
266         verifyValueEquals(UNIT, 1.0);
267         verifyValueEquals(UNIT, 1L);
268         verifyValueEquals(UNIT, 1);
269         verifyValueEquals(UNIT, (short)1);
270 
271         // Zero, simple case
272         verifyValueEquals(ZERO, 0.0f);
273         verifyValueEquals(ZERO, 0.0);
274         verifyValueEquals(ZERO, 0L);
275         verifyValueEquals(ZERO, 0);
276         verifyValueEquals(ZERO, (short)0);
277 
278         // NaN is 0 for integers, not-a-number for floating point
279         verifyValueEquals(NaN, Float.NaN);
280         verifyValueEquals(NaN, Double.NaN);
281         verifyValueEquals(NaN, 0L);
282         verifyValueEquals(NaN, 0);
283         verifyValueEquals(NaN, (short)0);
284 
285         // Positive infinity, saturates upwards for integers
286         verifyValueEquals(POSITIVE_INFINITY, Float.POSITIVE_INFINITY);
287         verifyValueEquals(POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
288         verifyValueEquals(POSITIVE_INFINITY, Long.MAX_VALUE);
289         verifyValueEquals(POSITIVE_INFINITY, Integer.MAX_VALUE);
290         verifyValueEquals(POSITIVE_INFINITY, (short)-1);
291 
292         // Negative infinity, saturates downwards for integers
293         verifyValueEquals(NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
294         verifyValueEquals(NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
295         verifyValueEquals(NEGATIVE_INFINITY, Long.MIN_VALUE);
296         verifyValueEquals(NEGATIVE_INFINITY, Integer.MIN_VALUE);
297         verifyValueEquals(NEGATIVE_INFINITY, (short)0);
298 
299         // Normal finite values, round down for integers
300         final Rational oneQuarter = new Rational(1, 4);
301         verifyValueEquals(oneQuarter, 1.0f / 4.0f);
302         verifyValueEquals(oneQuarter, 1.0 / 4.0);
303         verifyValueEquals(oneQuarter, 0L);
304         verifyValueEquals(oneQuarter, 0);
305         verifyValueEquals(oneQuarter, (short)0);
306 
307         final Rational nineFifths = new Rational(9, 5);
308         verifyValueEquals(nineFifths, 9.0f / 5.0f);
309         verifyValueEquals(nineFifths, 9.0 / 5.0);
310         verifyValueEquals(nineFifths, 1L);
311         verifyValueEquals(nineFifths, 1);
312         verifyValueEquals(nineFifths, (short)1);
313 
314         final Rational negativeHundred = new Rational(-1000, 10);
315         verifyValueEquals(negativeHundred, -100.f / 1.f);
316         verifyValueEquals(negativeHundred, -100.0 / 1.0);
317         verifyValueEquals(negativeHundred, -100L);
318         verifyValueEquals(negativeHundred, -100);
319         verifyValueEquals(negativeHundred, (short)-100);
320 
321         // Short truncates if the result is too large
322         verifyValueEquals(new Rational(Integer.MAX_VALUE, 1), (short)Integer.MAX_VALUE);
323         verifyValueEquals(new Rational(0x00FFFFFF, 1), (short)0x00FFFFFF);
324         verifyValueEquals(new Rational(0x00FF00FF, 1), (short)0x00FF00FF);
325     }
326 
327     @Test
testSerialize()328     public void testSerialize() throws ClassNotFoundException, IOException {
329         /*
330          * Check correct [de]serialization
331          */
332         verifyEqualsAfterSerializing(ZERO);
333         verifyEqualsAfterSerializing(NaN);
334         verifyEqualsAfterSerializing(NEGATIVE_INFINITY);
335         verifyEqualsAfterSerializing(POSITIVE_INFINITY);
336         verifyEqualsAfterSerializing(UNIT);
337         verifyEqualsAfterSerializing(new Rational(100, 200));
338         verifyEqualsAfterSerializing(new Rational(-100, 200));
339         verifyEqualsAfterSerializing(new Rational(5, 1));
340         verifyEqualsAfterSerializing(new Rational(Integer.MAX_VALUE, Integer.MIN_VALUE));
341 
342         /*
343          * Check bad deserialization fails
344          */
345         try {
346             Rational badZero = createIllegalRational(0, 100); // [0, 100] , should be [0, 1]
347             Rational results = serializeRoundTrip(badZero);
348             fail("Deserializing " + results + " should not have succeeded");
349         } catch (InvalidObjectException e) {
350             // OK
351         }
352 
353         try {
354             Rational badPosInfinity = createIllegalRational(100, 0); // [100, 0] , should be [1, 0]
355             Rational results = serializeRoundTrip(badPosInfinity);
356             fail("Deserializing " + results + " should not have succeeded");
357         } catch (InvalidObjectException e) {
358             // OK
359         }
360 
361         try {
362             Rational badNegInfinity =
363                     createIllegalRational(-100, 0); // [-100, 0] , should be [-1, 0]
364             Rational results = serializeRoundTrip(badNegInfinity);
365             fail("Deserializing " + results + " should not have succeeded");
366         } catch (InvalidObjectException e) {
367             // OK
368         }
369 
370         try {
371             Rational badReduced = createIllegalRational(2, 4); // [2,4] , should be [1, 2]
372             Rational results = serializeRoundTrip(badReduced);
373             fail("Deserializing " + results + " should not have succeeded");
374         } catch (InvalidObjectException e) {
375             // OK
376         }
377 
378         try {
379             Rational badReducedNeg = createIllegalRational(-2, 4); // [-2, 4] should be [-1, 2]
380             Rational results = serializeRoundTrip(badReducedNeg);
381             fail("Deserializing " + results + " should not have succeeded");
382         } catch (InvalidObjectException e) {
383             // OK
384         }
385     }
386 
387     @Test
testParseRational()388     public void testParseRational() {
389         assertEquals(new Rational(1, 2), Rational.parseRational("3:+6"));
390         assertEquals(new Rational(1, 2), Rational.parseRational("-3:-6"));
391         assertEquals(Rational.NaN, Rational.parseRational("NaN"));
392         assertEquals(Rational.POSITIVE_INFINITY, Rational.parseRational("Infinity"));
393         assertEquals(Rational.NEGATIVE_INFINITY, Rational.parseRational("-Infinity"));
394         assertEquals(Rational.ZERO, Rational.parseRational("0/261"));
395         assertEquals(Rational.NaN, Rational.parseRational("0/-0"));
396         assertEquals(Rational.POSITIVE_INFINITY, Rational.parseRational("1000/+0"));
397         assertEquals(Rational.NEGATIVE_INFINITY, Rational.parseRational("-1000/-0"));
398 
399         Rational r = new Rational(10, 15);
400         assertEquals(r, Rational.parseRational(r.toString()));
401     }
402 
403     @Test(expected=NumberFormatException.class)
testParseRationalInvalid1()404     public void testParseRationalInvalid1() {
405         Rational.parseRational("1.5");
406     }
407 
408     @Test(expected=NumberFormatException.class)
testParseRationalInvalid2()409     public void testParseRationalInvalid2() {
410         Rational.parseRational("239");
411     }
412 
verifyValueEquals(Rational object, float expected)413     private static void verifyValueEquals(Rational object, float expected) {
414         assertEquals("Checking floatValue() for " + object + ";",
415                 expected, object.floatValue(), 0.0f);
416     }
417 
verifyValueEquals(Rational object, double expected)418     private static void verifyValueEquals(Rational object, double expected) {
419         assertEquals("Checking doubleValue() for " + object + ";",
420                 expected, object.doubleValue(), 0.0f);
421     }
422 
verifyValueEquals(Rational object, long expected)423     private static void verifyValueEquals(Rational object, long expected) {
424         assertEquals("Checking longValue() for " + object + ";",
425                 expected, object.longValue());
426     }
427 
verifyValueEquals(Rational object, int expected)428     private static void verifyValueEquals(Rational object, int expected) {
429         assertEquals("Checking intValue() for " + object + ";",
430                 expected, object.intValue());
431     }
432 
verifyValueEquals(Rational object, short expected)433     private static void verifyValueEquals(Rational object, short expected) {
434         assertEquals("Checking shortValue() for " + object + ";",
435                 expected, object.shortValue());
436     }
437 
verifyFinite(Rational object, boolean expected)438     private static void verifyFinite(Rational object, boolean expected) {
439         verifyAction("finite", object, expected, object.isFinite());
440     }
441 
verifyInfinite(Rational object, boolean expected)442     private static void verifyInfinite(Rational object, boolean expected) {
443         verifyAction("infinite", object, expected, object.isInfinite());
444     }
445 
verifyNaN(Rational object, boolean expected)446     private static void verifyNaN(Rational object, boolean expected) {
447         verifyAction("NaN", object, expected, object.isNaN());
448     }
449 
verifyZero(Rational object, boolean expected)450     private static void verifyZero(Rational object, boolean expected) {
451         verifyAction("zero", object, expected, object.isZero());
452     }
453 
verifyAction(String action, T object, boolean expected, boolean actual)454     private static <T> void verifyAction(String action, T object, boolean expected,
455             boolean actual) {
456         String expectedMessage = expected ? action : ("not " + action);
457         assertEquals("Expected " + object + " to be " + expectedMessage,
458                 expected, actual);
459     }
460 
verifyLessThan(T left, T right)461     private static <T extends Comparable<? super T>> void verifyLessThan(T left, T right) {
462         assertTrue("Expected (LR) left " + left + " to be less than right " + right,
463                 left.compareTo(right) < 0);
464         assertTrue("Expected (RL) left " + left + " to be less than right " + right,
465                 right.compareTo(left) > 0);
466     }
467 
verifyGreaterThan(T left, T right)468     private static <T extends Comparable<? super T>> void verifyGreaterThan(T left, T right) {
469         assertTrue("Expected (LR) left " + left + " to be greater than right " + right,
470                 left.compareTo(right) > 0);
471         assertTrue("Expected (RL) left " + left + " to be greater than right " + right,
472                 right.compareTo(left) < 0);
473     }
474 
verifyCompareEquals(T left, T right)475     private static <T extends Comparable<? super T>> void verifyCompareEquals(T left, T right) {
476         assertTrue("Expected (LR) left " + left + " to be compareEquals to right " + right,
477                 left.compareTo(right) == 0);
478         assertTrue("Expected (RL) left " + left + " to be compareEquals to right " + right,
479                 right.compareTo(left) == 0);
480     }
481 
serialize(T obj)482     private static <T extends Serializable> byte[] serialize(T obj) throws IOException {
483         ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
484         try (ObjectOutputStream objectStream = new ObjectOutputStream(byteStream)) {
485             objectStream.writeObject(obj);
486         }
487         return byteStream.toByteArray();
488     }
489 
deserialize(byte[] array, Class<T> klass)490     private static <T extends Serializable> T deserialize(byte[] array, Class<T> klass)
491             throws IOException, ClassNotFoundException {
492         ByteArrayInputStream bais = new ByteArrayInputStream(array);
493         ObjectInputStream ois = new ObjectInputStream(bais);
494         Object obj = ois.readObject();
495         return klass.cast(obj);
496     }
497 
498     @SuppressWarnings("unchecked")
serializeRoundTrip(T obj)499     private static <T extends Serializable> T serializeRoundTrip(T obj)
500             throws IOException, ClassNotFoundException {
501         Class<T> klass = (Class<T>) obj.getClass();
502         byte[] arr = serialize(obj);
503         T serialized = deserialize(arr, klass);
504         return serialized;
505     }
506 
verifyEqualsAfterSerializing(T obj)507     private static <T extends Serializable> void verifyEqualsAfterSerializing(T obj)
508             throws ClassNotFoundException, IOException {
509         T serialized = serializeRoundTrip(obj);
510         assertEquals("Expected values to be equal after serialization round-trip", obj, serialized);
511     }
512 
createIllegalRational(int numerator, int denominator)513     private static Rational createIllegalRational(int numerator, int denominator) {
514         Rational r = new Rational(numerator, denominator);
515         mutateField(r, "mNumerator", numerator);
516         mutateField(r, "mDenominator", denominator);
517         return r;
518     }
519 
mutateField(T object, String name, int value)520     private static <T> void mutateField(T object, String name, int value) {
521         try {
522             Field f = object.getClass().getDeclaredField(name);
523             f.setAccessible(true);
524             f.set(object, value);
525         } catch (NoSuchFieldException e) {
526             throw new AssertionError(e);
527         } catch (IllegalAccessException e) {
528             throw new AssertionError(e);
529         } catch (IllegalArgumentException e) {
530             throw new AssertionError(e);
531         }
532     }
533 }
534