diff options
Diffstat (limited to 'src/main/java/org/apache/commons/math3/fraction/Fraction.java')
-rw-r--r-- | src/main/java/org/apache/commons/math3/fraction/Fraction.java | 669 |
1 files changed, 669 insertions, 0 deletions
diff --git a/src/main/java/org/apache/commons/math3/fraction/Fraction.java b/src/main/java/org/apache/commons/math3/fraction/Fraction.java new file mode 100644 index 0000000..9b04e12 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/fraction/Fraction.java @@ -0,0 +1,669 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.commons.math3.fraction; + +import org.apache.commons.math3.FieldElement; +import org.apache.commons.math3.exception.MathArithmeticException; +import org.apache.commons.math3.exception.NullArgumentException; +import org.apache.commons.math3.exception.util.LocalizedFormats; +import org.apache.commons.math3.util.ArithmeticUtils; +import org.apache.commons.math3.util.FastMath; + +import java.io.Serializable; +import java.math.BigInteger; + +/** + * Representation of a rational number. + * + * <p>implements Serializable since 2.0 + * + * @since 1.1 + */ +public class Fraction extends Number + implements FieldElement<Fraction>, Comparable<Fraction>, Serializable { + + /** A fraction representing "2 / 1". */ + public static final Fraction TWO = new Fraction(2, 1); + + /** A fraction representing "1". */ + public static final Fraction ONE = new Fraction(1, 1); + + /** A fraction representing "0". */ + public static final Fraction ZERO = new Fraction(0, 1); + + /** A fraction representing "4/5". */ + public static final Fraction FOUR_FIFTHS = new Fraction(4, 5); + + /** A fraction representing "1/5". */ + public static final Fraction ONE_FIFTH = new Fraction(1, 5); + + /** A fraction representing "1/2". */ + public static final Fraction ONE_HALF = new Fraction(1, 2); + + /** A fraction representing "1/4". */ + public static final Fraction ONE_QUARTER = new Fraction(1, 4); + + /** A fraction representing "1/3". */ + public static final Fraction ONE_THIRD = new Fraction(1, 3); + + /** A fraction representing "3/5". */ + public static final Fraction THREE_FIFTHS = new Fraction(3, 5); + + /** A fraction representing "3/4". */ + public static final Fraction THREE_QUARTERS = new Fraction(3, 4); + + /** A fraction representing "2/5". */ + public static final Fraction TWO_FIFTHS = new Fraction(2, 5); + + /** A fraction representing "2/4". */ + public static final Fraction TWO_QUARTERS = new Fraction(2, 4); + + /** A fraction representing "2/3". */ + public static final Fraction TWO_THIRDS = new Fraction(2, 3); + + /** A fraction representing "-1 / 1". */ + public static final Fraction MINUS_ONE = new Fraction(-1, 1); + + /** Serializable version identifier */ + private static final long serialVersionUID = 3698073679419233275L; + + /** The default epsilon used for convergence. */ + private static final double DEFAULT_EPSILON = 1e-5; + + /** The denominator. */ + private final int denominator; + + /** The numerator. */ + private final int numerator; + + /** + * Create a fraction given the double value. + * + * @param value the double value to convert to a fraction. + * @throws FractionConversionException if the continued fraction failed to converge. + */ + public Fraction(double value) throws FractionConversionException { + this(value, DEFAULT_EPSILON, 100); + } + + /** + * Create a fraction given the double value and maximum error allowed. + * + * <p>References: + * + * <ul> + * <li><a href="http://mathworld.wolfram.com/ContinuedFraction.html">Continued Fraction</a> + * equations (11) and (22)-(26) + * </ul> + * + * @param value the double value to convert to a fraction. + * @param epsilon maximum error allowed. The resulting fraction is within {@code epsilon} of + * {@code value}, in absolute terms. + * @param maxIterations maximum number of convergents + * @throws FractionConversionException if the continued fraction failed to converge. + */ + public Fraction(double value, double epsilon, int maxIterations) + throws FractionConversionException { + this(value, epsilon, Integer.MAX_VALUE, maxIterations); + } + + /** + * Create a fraction given the double value and maximum denominator. + * + * <p>References: + * + * <ul> + * <li><a href="http://mathworld.wolfram.com/ContinuedFraction.html">Continued Fraction</a> + * equations (11) and (22)-(26) + * </ul> + * + * @param value the double value to convert to a fraction. + * @param maxDenominator The maximum allowed value for denominator + * @throws FractionConversionException if the continued fraction failed to converge + */ + public Fraction(double value, int maxDenominator) throws FractionConversionException { + this(value, 0, maxDenominator, 100); + } + + /** + * Create a fraction given the double value and either the maximum error allowed or the maximum + * number of denominator digits. + * + * <p>NOTE: This constructor is called with EITHER - a valid epsilon value and the + * maxDenominator set to Integer.MAX_VALUE (that way the maxDenominator has no effect). OR - a + * valid maxDenominator value and the epsilon value set to zero (that way epsilon only has + * effect if there is an exact match before the maxDenominator value is reached). + * + * <p>It has been done this way so that the same code can be (re)used for both scenarios. + * However this could be confusing to users if it were part of the public API and this + * constructor should therefore remain PRIVATE. See JIRA issue ticket MATH-181 for more details: + * + * <p>https://issues.apache.org/jira/browse/MATH-181 + * + * @param value the double value to convert to a fraction. + * @param epsilon maximum error allowed. The resulting fraction is within {@code epsilon} of + * {@code value}, in absolute terms. + * @param maxDenominator maximum denominator value allowed. + * @param maxIterations maximum number of convergents + * @throws FractionConversionException if the continued fraction failed to converge. + */ + private Fraction(double value, double epsilon, int maxDenominator, int maxIterations) + throws FractionConversionException { + long overflow = Integer.MAX_VALUE; + double r0 = value; + long a0 = (long) FastMath.floor(r0); + if (FastMath.abs(a0) > overflow) { + throw new FractionConversionException(value, a0, 1l); + } + + // check for (almost) integer arguments, which should not go to iterations. + if (FastMath.abs(a0 - value) < epsilon) { + this.numerator = (int) a0; + this.denominator = 1; + return; + } + + long p0 = 1; + long q0 = 0; + long p1 = a0; + long q1 = 1; + + long p2 = 0; + long q2 = 1; + + int n = 0; + boolean stop = false; + do { + ++n; + double r1 = 1.0 / (r0 - a0); + long a1 = (long) FastMath.floor(r1); + p2 = (a1 * p1) + p0; + q2 = (a1 * q1) + q0; + + if ((FastMath.abs(p2) > overflow) || (FastMath.abs(q2) > overflow)) { + // in maxDenominator mode, if the last fraction was very close to the actual value + // q2 may overflow in the next iteration; in this case return the last one. + if (epsilon == 0.0 && FastMath.abs(q1) < maxDenominator) { + break; + } + throw new FractionConversionException(value, p2, q2); + } + + double convergent = (double) p2 / (double) q2; + if (n < maxIterations + && FastMath.abs(convergent - value) > epsilon + && q2 < maxDenominator) { + p0 = p1; + p1 = p2; + q0 = q1; + q1 = q2; + a0 = a1; + r0 = r1; + } else { + stop = true; + } + } while (!stop); + + if (n >= maxIterations) { + throw new FractionConversionException(value, maxIterations); + } + + if (q2 < maxDenominator) { + this.numerator = (int) p2; + this.denominator = (int) q2; + } else { + this.numerator = (int) p1; + this.denominator = (int) q1; + } + } + + /** + * Create a fraction from an int. The fraction is num / 1. + * + * @param num the numerator. + */ + public Fraction(int num) { + this(num, 1); + } + + /** + * Create a fraction given the numerator and denominator. The fraction is reduced to lowest + * terms. + * + * @param num the numerator. + * @param den the denominator. + * @throws MathArithmeticException if the denominator is {@code zero} + */ + public Fraction(int num, int den) { + if (den == 0) { + throw new MathArithmeticException( + LocalizedFormats.ZERO_DENOMINATOR_IN_FRACTION, num, den); + } + if (den < 0) { + if (num == Integer.MIN_VALUE || den == Integer.MIN_VALUE) { + throw new MathArithmeticException(LocalizedFormats.OVERFLOW_IN_FRACTION, num, den); + } + num = -num; + den = -den; + } + // reduce numerator and denominator by greatest common denominator. + final int d = ArithmeticUtils.gcd(num, den); + if (d > 1) { + num /= d; + den /= d; + } + + // move sign to numerator. + if (den < 0) { + num = -num; + den = -den; + } + this.numerator = num; + this.denominator = den; + } + + /** + * Returns the absolute value of this fraction. + * + * @return the absolute value. + */ + public Fraction abs() { + Fraction ret; + if (numerator >= 0) { + ret = this; + } else { + ret = negate(); + } + return ret; + } + + /** + * Compares this object to another based on size. + * + * @param object the object to compare to + * @return -1 if this is less than {@code object}, +1 if this is greater than {@code object}, 0 + * if they are equal. + */ + public int compareTo(Fraction object) { + long nOd = ((long) numerator) * object.denominator; + long dOn = ((long) denominator) * object.numerator; + return (nOd < dOn) ? -1 : ((nOd > dOn) ? +1 : 0); + } + + /** + * Gets the fraction as a {@code double}. This calculates the fraction as the numerator divided + * by denominator. + * + * @return the fraction as a {@code double} + */ + @Override + public double doubleValue() { + return (double) numerator / (double) denominator; + } + + /** + * Test for the equality of two fractions. If the lowest term numerator and denominators are the + * same for both fractions, the two fractions are considered to be equal. + * + * @param other fraction to test for equality to this fraction + * @return true if two fractions are equal, false if object is {@code null}, not an instance of + * {@link Fraction}, or not equal to this fraction instance. + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof Fraction) { + // since fractions are always in lowest terms, numerators and + // denominators can be compared directly for equality. + Fraction rhs = (Fraction) other; + return (numerator == rhs.numerator) && (denominator == rhs.denominator); + } + return false; + } + + /** + * Gets the fraction as a {@code float}. This calculates the fraction as the numerator divided + * by denominator. + * + * @return the fraction as a {@code float} + */ + @Override + public float floatValue() { + return (float) doubleValue(); + } + + /** + * Access the denominator. + * + * @return the denominator. + */ + public int getDenominator() { + return denominator; + } + + /** + * Access the numerator. + * + * @return the numerator. + */ + public int getNumerator() { + return numerator; + } + + /** + * Gets a hashCode for the fraction. + * + * @return a hash code value for this object + */ + @Override + public int hashCode() { + return 37 * (37 * 17 + numerator) + denominator; + } + + /** + * Gets the fraction as an {@code int}. This returns the whole number part of the fraction. + * + * @return the whole number fraction part + */ + @Override + public int intValue() { + return (int) doubleValue(); + } + + /** + * Gets the fraction as a {@code long}. This returns the whole number part of the fraction. + * + * @return the whole number fraction part + */ + @Override + public long longValue() { + return (long) doubleValue(); + } + + /** + * Return the additive inverse of this fraction. + * + * @return the negation of this fraction. + */ + public Fraction negate() { + if (numerator == Integer.MIN_VALUE) { + throw new MathArithmeticException( + LocalizedFormats.OVERFLOW_IN_FRACTION, numerator, denominator); + } + return new Fraction(-numerator, denominator); + } + + /** + * Return the multiplicative inverse of this fraction. + * + * @return the reciprocal fraction + */ + public Fraction reciprocal() { + return new Fraction(denominator, numerator); + } + + /** + * Adds the value of this fraction to another, returning the result in reduced form. The + * algorithm follows Knuth, 4.5.1. + * + * @param fraction the fraction to add, must not be {@code null} + * @return a {@code Fraction} instance with the resulting values + * @throws NullArgumentException if the fraction is {@code null} + * @throws MathArithmeticException if the resulting numerator or denominator exceeds {@code + * Integer.MAX_VALUE} + */ + public Fraction add(Fraction fraction) { + return addSub(fraction, true /* add */); + } + + /** + * Add an integer to the fraction. + * + * @param i the {@code integer} to add. + * @return this + i + */ + public Fraction add(final int i) { + return new Fraction(numerator + i * denominator, denominator); + } + + /** + * Subtracts the value of another fraction from the value of this one, returning the result in + * reduced form. + * + * @param fraction the fraction to subtract, must not be {@code null} + * @return a {@code Fraction} instance with the resulting values + * @throws NullArgumentException if the fraction is {@code null} + * @throws MathArithmeticException if the resulting numerator or denominator cannot be + * represented in an {@code int}. + */ + public Fraction subtract(Fraction fraction) { + return addSub(fraction, false /* subtract */); + } + + /** + * Subtract an integer from the fraction. + * + * @param i the {@code integer} to subtract. + * @return this - i + */ + public Fraction subtract(final int i) { + return new Fraction(numerator - i * denominator, denominator); + } + + /** + * Implement add and subtract using algorithm described in Knuth 4.5.1. + * + * @param fraction the fraction to subtract, must not be {@code null} + * @param isAdd true to add, false to subtract + * @return a {@code Fraction} instance with the resulting values + * @throws NullArgumentException if the fraction is {@code null} + * @throws MathArithmeticException if the resulting numerator or denominator cannot be + * represented in an {@code int}. + */ + private Fraction addSub(Fraction fraction, boolean isAdd) { + if (fraction == null) { + throw new NullArgumentException(LocalizedFormats.FRACTION); + } + // zero is identity for addition. + if (numerator == 0) { + return isAdd ? fraction : fraction.negate(); + } + if (fraction.numerator == 0) { + return this; + } + // if denominators are randomly distributed, d1 will be 1 about 61% + // of the time. + int d1 = ArithmeticUtils.gcd(denominator, fraction.denominator); + if (d1 == 1) { + // result is ( (u*v' +/- u'v) / u'v') + int uvp = ArithmeticUtils.mulAndCheck(numerator, fraction.denominator); + int upv = ArithmeticUtils.mulAndCheck(fraction.numerator, denominator); + return new Fraction( + isAdd + ? ArithmeticUtils.addAndCheck(uvp, upv) + : ArithmeticUtils.subAndCheck(uvp, upv), + ArithmeticUtils.mulAndCheck(denominator, fraction.denominator)); + } + // the quantity 't' requires 65 bits of precision; see knuth 4.5.1 + // exercise 7. we're going to use a BigInteger. + // t = u(v'/d1) +/- v(u'/d1) + BigInteger uvp = + BigInteger.valueOf(numerator) + .multiply(BigInteger.valueOf(fraction.denominator / d1)); + BigInteger upv = + BigInteger.valueOf(fraction.numerator) + .multiply(BigInteger.valueOf(denominator / d1)); + BigInteger t = isAdd ? uvp.add(upv) : uvp.subtract(upv); + // but d2 doesn't need extra precision because + // d2 = gcd(t,d1) = gcd(t mod d1, d1) + int tmodd1 = t.mod(BigInteger.valueOf(d1)).intValue(); + int d2 = (tmodd1 == 0) ? d1 : ArithmeticUtils.gcd(tmodd1, d1); + + // result is (t/d2) / (u'/d1)(v'/d2) + BigInteger w = t.divide(BigInteger.valueOf(d2)); + if (w.bitLength() > 31) { + throw new MathArithmeticException( + LocalizedFormats.NUMERATOR_OVERFLOW_AFTER_MULTIPLY, w); + } + return new Fraction( + w.intValue(), + ArithmeticUtils.mulAndCheck(denominator / d1, fraction.denominator / d2)); + } + + /** + * Multiplies the value of this fraction by another, returning the result in reduced form. + * + * @param fraction the fraction to multiply by, must not be {@code null} + * @return a {@code Fraction} instance with the resulting values + * @throws NullArgumentException if the fraction is {@code null} + * @throws MathArithmeticException if the resulting numerator or denominator exceeds {@code + * Integer.MAX_VALUE} + */ + public Fraction multiply(Fraction fraction) { + if (fraction == null) { + throw new NullArgumentException(LocalizedFormats.FRACTION); + } + if (numerator == 0 || fraction.numerator == 0) { + return ZERO; + } + // knuth 4.5.1 + // make sure we don't overflow unless the result *must* overflow. + int d1 = ArithmeticUtils.gcd(numerator, fraction.denominator); + int d2 = ArithmeticUtils.gcd(fraction.numerator, denominator); + return getReducedFraction( + ArithmeticUtils.mulAndCheck(numerator / d1, fraction.numerator / d2), + ArithmeticUtils.mulAndCheck(denominator / d2, fraction.denominator / d1)); + } + + /** + * Multiply the fraction by an integer. + * + * @param i the {@code integer} to multiply by. + * @return this * i + */ + public Fraction multiply(final int i) { + return multiply(new Fraction(i)); + } + + /** + * Divide the value of this fraction by another. + * + * @param fraction the fraction to divide by, must not be {@code null} + * @return a {@code Fraction} instance with the resulting values + * @throws IllegalArgumentException if the fraction is {@code null} + * @throws MathArithmeticException if the fraction to divide by is zero + * @throws MathArithmeticException if the resulting numerator or denominator exceeds {@code + * Integer.MAX_VALUE} + */ + public Fraction divide(Fraction fraction) { + if (fraction == null) { + throw new NullArgumentException(LocalizedFormats.FRACTION); + } + if (fraction.numerator == 0) { + throw new MathArithmeticException( + LocalizedFormats.ZERO_FRACTION_TO_DIVIDE_BY, + fraction.numerator, + fraction.denominator); + } + return multiply(fraction.reciprocal()); + } + + /** + * Divide the fraction by an integer. + * + * @param i the {@code integer} to divide by. + * @return this * i + */ + public Fraction divide(final int i) { + return divide(new Fraction(i)); + } + + /** + * Gets the fraction percentage as a {@code double}. This calculates the fraction as the + * numerator divided by denominator multiplied by 100. + * + * @return the fraction percentage as a {@code double}. + */ + public double percentageValue() { + return 100 * doubleValue(); + } + + /** + * Creates a {@code Fraction} instance with the 2 parts of a fraction Y/Z. + * + * <p>Any negative signs are resolved to be on the numerator. + * + * @param numerator the numerator, for example the three in 'three sevenths' + * @param denominator the denominator, for example the seven in 'three sevenths' + * @return a new fraction instance, with the numerator and denominator reduced + * @throws MathArithmeticException if the denominator is {@code zero} + */ + public static Fraction getReducedFraction(int numerator, int denominator) { + if (denominator == 0) { + throw new MathArithmeticException( + LocalizedFormats.ZERO_DENOMINATOR_IN_FRACTION, numerator, denominator); + } + if (numerator == 0) { + return ZERO; // normalize zero. + } + // allow 2^k/-2^31 as a valid fraction (where k>0) + if (denominator == Integer.MIN_VALUE && (numerator & 1) == 0) { + numerator /= 2; + denominator /= 2; + } + if (denominator < 0) { + if (numerator == Integer.MIN_VALUE || denominator == Integer.MIN_VALUE) { + throw new MathArithmeticException( + LocalizedFormats.OVERFLOW_IN_FRACTION, numerator, denominator); + } + numerator = -numerator; + denominator = -denominator; + } + // simplify fraction. + int gcd = ArithmeticUtils.gcd(numerator, denominator); + numerator /= gcd; + denominator /= gcd; + return new Fraction(numerator, denominator); + } + + /** + * Returns the {@code String} representing this fraction, ie "num / dem" or just "num" if the + * denominator is one. + * + * @return a string representation of the fraction. + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + String str = null; + if (denominator == 1) { + str = Integer.toString(numerator); + } else if (numerator == 0) { + str = "0"; + } else { + str = numerator + " / " + denominator; + } + return str; + } + + /** {@inheritDoc} */ + public FractionField getField() { + return FractionField.getInstance(); + } +} |