Rational.java revision 72f9f0a96e4476ef231d5001cb30521ad4ce5b1e
1/*
2 * Copyright (C) 2013 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 */
16package android.util;
17
18/**
19 * <p>An immutable data type representation a rational number.</p>
20 *
21 * <p>Contains a pair of {@code int}s representing the numerator and denominator of a
22 * Rational number. </p>
23 */
24public final class Rational {
25    private final int mNumerator;
26    private final int mDenominator;
27
28    /**
29     * <p>Create a Rational with a given numerator and denominator.</p>
30     *
31     * <p>The signs of the numerator and the denominator may be flipped such that the denominator
32     * is always positive.</p>
33     *
34     * <p>A rational value with a 0-denominator may be constructed, but will have similar semantics
35     * as float {@code NaN} and {@code INF} values. For {@code NaN},
36     * both {@link #getNumerator} and {@link #getDenominator} functions will return 0. For
37     * positive or negative {@code INF}, only the {@link #getDenominator} will return 0.</p>
38     *
39     * @param numerator the numerator of the rational
40     * @param denominator the denominator of the rational
41     */
42    public Rational(int numerator, int denominator) {
43
44        if (denominator < 0) {
45            numerator = -numerator;
46            denominator = -denominator;
47        }
48
49        mNumerator = numerator;
50        mDenominator = denominator;
51    }
52
53    /**
54     * Gets the numerator of the rational.
55     */
56    public int getNumerator() {
57        if (mDenominator == 0) {
58            return 0;
59        }
60        return mNumerator;
61    }
62
63    /**
64     * Gets the denominator of the rational
65     */
66    public int getDenominator() {
67        return mDenominator;
68    }
69
70    private boolean isNaN() {
71        return mDenominator == 0 && mNumerator == 0;
72    }
73
74    private boolean isInf() {
75        return mDenominator == 0 && mNumerator > 0;
76    }
77
78    private boolean isNegInf() {
79        return mDenominator == 0 && mNumerator < 0;
80    }
81
82    /**
83     * <p>Compare this Rational to another object and see if they are equal.</p>
84     *
85     * <p>A Rational object can only be equal to another Rational object (comparing against any other
86     * type will return false).</p>
87     *
88     * <p>A Rational object is considered equal to another Rational object if and only if one of
89     * the following holds</p>:
90     * <ul><li>Both are NaN</li>
91     *     <li>Both are infinities of the same sign</li>
92     *     <li>Both have the same numerator and denominator in their reduced form</li>
93     * </ul>
94     *
95     * <p>A reduced form of a Rational is calculated by dividing both the numerator and the
96     * denominator by their greatest common divisor.</p>
97     *
98     * <pre>{@code
99     *      (new Rational(1, 2)).equals(new Rational(1, 2)) == true   // trivially true
100     *      (new Rational(2, 3)).equals(new Rational(1, 2)) == false  // trivially false
101     *      (new Rational(1, 2)).equals(new Rational(2, 4)) == true   // true after reduction
102     *      (new Rational(0, 0)).equals(new Rational(0, 0)) == true   // NaN.equals(NaN)
103     *      (new Rational(1, 0)).equals(new Rational(5, 0)) == true   // both are +infinity
104     *      (new Rational(1, 0)).equals(new Rational(-1, 0)) == false // +infinity != -infinity
105     * }</pre>
106     *
107     * @param obj a reference to another object
108     *
109     * @return A boolean that determines whether or not the two Rational objects are equal.
110     */
111    @Override
112    public boolean equals(Object obj) {
113        if (obj == null) {
114            return false;
115        } else if (obj instanceof Rational) {
116            Rational other = (Rational) obj;
117            if (mDenominator == 0 || other.mDenominator == 0) {
118                if (isNaN() && other.isNaN()) {
119                    return true;
120                } else if (isInf() && other.isInf() || isNegInf() && other.isNegInf()) {
121                    return true;
122                } else {
123                    return false;
124                }
125            } else if (mNumerator == other.mNumerator && mDenominator == other.mDenominator) {
126                return true;
127            } else {
128                int thisGcd = gcd();
129                int otherGcd = other.gcd();
130
131                int thisNumerator = mNumerator / thisGcd;
132                int thisDenominator = mDenominator / thisGcd;
133
134                int otherNumerator = other.mNumerator / otherGcd;
135                int otherDenominator = other.mDenominator / otherGcd;
136
137                return (thisNumerator == otherNumerator && thisDenominator == otherDenominator);
138            }
139        }
140        return false;
141    }
142
143    @Override
144    public String toString() {
145        if (isNaN()) {
146            return "NaN";
147        } else if (isInf()) {
148            return "Infinity";
149        } else if (isNegInf()) {
150            return "-Infinity";
151        } else {
152            return mNumerator + "/" + mDenominator;
153        }
154    }
155
156    /**
157     * <p>Convert to a floating point representation.</p>
158     *
159     * @return The floating point representation of this rational number.
160     * @hide
161     */
162    public float toFloat() {
163        return (float) mNumerator / (float) mDenominator;
164    }
165
166    /**
167     * {@inheritDoc}
168     */
169    @Override
170    public int hashCode() {
171        // Bias the hash code for the first (2^16) values for both numerator and denominator
172        int numeratorFlipped = mNumerator << 16 | mNumerator >>> 16;
173
174        return mDenominator ^ numeratorFlipped;
175    }
176
177    /**
178     * Calculates the greatest common divisor using Euclid's algorithm.
179     *
180     * @return An int value representing the gcd. Always positive.
181     * @hide
182     */
183    public int gcd() {
184        /**
185         * Non-recursive implementation of Euclid's algorithm:
186         *
187         *  gcd(a, 0) := a
188         *  gcd(a, b) := gcd(b, a mod b)
189         *
190         */
191
192        int a = mNumerator;
193        int b = mDenominator;
194
195        while (b != 0) {
196            int oldB = b;
197
198            b = a % b;
199            a = oldB;
200        }
201
202        return Math.abs(a);
203    }
204}
205