From 21a95478c2057bc6d2086a9e4904c0335ca72a03 Mon Sep 17 00:00:00 2001 From: Luc Maisonobe Date: Mon, 12 Feb 2007 19:23:17 +0000 Subject: [PATCH] Added and used a specialized exception for continued fraction convergence errors git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/math/trunk@506591 13f79535-47bb-0310-9956-ffa450edef68 --- .../commons/math/fraction/Fraction.java | 971 +++++++++--------- .../fraction/FractionConversionException.java | 22 + 2 files changed, 507 insertions(+), 486 deletions(-) create mode 100644 src/java/org/apache/commons/math/fraction/FractionConversionException.java diff --git a/src/java/org/apache/commons/math/fraction/Fraction.java b/src/java/org/apache/commons/math/fraction/Fraction.java index 22241f47e..302cde6e3 100644 --- a/src/java/org/apache/commons/math/fraction/Fraction.java +++ b/src/java/org/apache/commons/math/fraction/Fraction.java @@ -1,486 +1,485 @@ -/* - * 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.math.fraction; - -import java.math.BigInteger; -import org.apache.commons.math.ConvergenceException; -import org.apache.commons.math.util.MathUtils; - -/** - * Representation of a rational number. - * - * @since 1.1 - * @version $Revision$ $Date$ - */ -public class Fraction extends Number implements Comparable { - - /** A fraction representing "1 / 1". */ - public static final Fraction ONE = new Fraction(1, 1); - - /** A fraction representing "0 / 1". */ - public static final Fraction ZERO = new Fraction(0, 1); - - /** Serializable version identifier */ - private static final long serialVersionUID = 65382027393090L; - - /** The denominator. */ - private int denominator; - - /** The numerator. */ - private int numerator; - - /** - * Create a fraction given the double value. - * @param value the double value to convert to a fraction. - * @throws ConvergenceException if the continued fraction failed to - * converge. - */ - public Fraction(double value) throws ConvergenceException { - this(value, 1.0e-5, 100); - } - - /** - * Create a fraction given the double value. - *

- * References: - *

- *

- * @param value the double value to convert to a fraction. - * @param epsilon maximum error allowed. The resulting fraction is within - * epsilon of value, in absolute terms. - * @param maxIterations maximum number of convergents - * @throws ConvergenceException if the continued fraction failed to - * converge. - */ - public Fraction(double value, double epsilon, int maxIterations) - throws ConvergenceException - { - double r0 = value; - int a0 = (int)Math.floor(r0); - - // check for (almost) integer arguments, which should not go - // to iterations. - if (Math.abs(a0 - value) < epsilon) { - this.numerator = a0; - this.denominator = 1; - return; - } - - int p0 = 1; - int q0 = 0; - int p1 = a0; - int q1 = 1; - - int p2 = 0; - int q2 = 1; - - int n = 0; - boolean stop = false; - do { - ++n; - double r1 = 1.0 / (r0 - a0); - int a1 = (int)Math.floor(r1); - p2 = (a1 * p1) + p0; - q2 = (a1 * q1) + q0; - - double convergent = (double)p2 / (double)q2; - if (n < maxIterations && Math.abs(convergent - value) > epsilon) { - p0 = p1; - p1 = p2; - q0 = q1; - q1 = q2; - a0 = a1; - r0 = r1; - } else { - stop = true; - } - } while (!stop); - - if (n >= maxIterations) { - throw new ConvergenceException( - "Unable to convert double to fraction"); - } - - this.numerator = p2; - this.denominator = q2; - reduce(); - } - - /** - * Create a fraction given the numerator and denominator. The fraction is - * reduced to lowest terms. - * @param num the numerator. - * @param den the denominator. - * @throws ArithmeticException if the denomiator is zero - */ - public Fraction(int num, int den) { - super(); - if (den == 0) { - throw new ArithmeticException("The denominator must not be zero"); - } - if (den < 0) { - if (num == Integer.MIN_VALUE || - den == Integer.MIN_VALUE) { - throw new ArithmeticException("overflow: can't negate"); - } - num = -num; - den = -den; - } - this.numerator = num; - this.denominator = den; - reduce(); - } - - /** - * 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 object, +1 if this is greater - * than object, 0 if they are equal. - */ - public int compareTo(Object object) { - int ret = 0; - - if (this != object) { - Fraction other = (Fraction)object; - double first = doubleValue(); - double second = other.doubleValue(); - - if (first < second) { - ret = -1; - } else if (first > second) { - ret = 1; - } - } - - return ret; - } - - /** - * Gets the fraction as a double. This calculates the fraction as - * the numerator divided by denominator. - * @return the fraction as a double - */ - 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 - * null, not an instance of {@link Fraction}, or not equal - * to this fraction instance. - */ - public boolean equals(Object other) { - boolean ret; - - if (this == other) { - ret = true; - } else if (other == null) { - ret = false; - } else { - try { - // since fractions are always in lowest terms, numerators and - // denominators can be compared directly for equality. - Fraction rhs = (Fraction)other; - ret = (numerator == rhs.numerator) && - (denominator == rhs.denominator); - } catch (ClassCastException ex) { - // ignore exception - ret = false; - } - } - - return ret; - } - - /** - * Gets the fraction as a float. This calculates the fraction as - * the numerator divided by denominator. - * @return the fraction as a float - */ - 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 - */ - public int hashCode() { - return 37 * (37 * 17 + getNumerator()) + getDenominator(); - } - - /** - * Gets the fraction as an int. This returns the whole number part - * of the fraction. - * @return the whole number fraction part - */ - public int intValue() { - return (int)doubleValue(); - } - - /** - * Gets the fraction as a long. This returns the whole number part - * of the fraction. - * @return the whole number fraction part - */ - 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 ArithmeticException("overflow: too large to negate"); - } - 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 null - * @return a Fraction instance with the resulting values - * @throws IllegalArgumentException if the fraction is null - * @throws ArithmeticException if the resulting numerator or denominator exceeds - * Integer.MAX_VALUE - */ - public Fraction add(Fraction fraction) { - return addSub(fraction, true /* add */); - } - - /** - *

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 null - * @return a Fraction instance with the resulting values - * @throws IllegalArgumentException if the fraction is null - * @throws ArithmeticException if the resulting numerator or denominator - * cannot be represented in an int. - */ - public Fraction subtract(Fraction fraction) { - return addSub(fraction, false /* subtract */); - } - - /** - * Implement add and subtract using algorithm described in Knuth 4.5.1. - * - * @param fraction the fraction to subtract, must not be null - * @param isAdd true to add, false to subtract - * @return a Fraction instance with the resulting values - * @throws IllegalArgumentException if the fraction is null - * @throws ArithmeticException if the resulting numerator or denominator - * cannot be represented in an int. - */ - private Fraction addSub(Fraction fraction, boolean isAdd) { - if (fraction == null) { - throw new IllegalArgumentException("The fraction must not be null"); - } - // 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 = MathUtils.gcd(denominator, fraction.denominator); - if (d1==1) { - // result is ( (u*v' +/- u'v) / u'v') - int uvp = MathUtils.mulAndCheck(numerator, fraction.denominator); - int upv = MathUtils.mulAndCheck(fraction.numerator, denominator); - return new Fraction - (isAdd ? MathUtils.addAndCheck(uvp, upv) : - MathUtils.subAndCheck(uvp, upv), - MathUtils.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:MathUtils.gcd(tmodd1, d1); - - // result is (t/d2) / (u'/d1)(v'/d2) - BigInteger w = t.divide(BigInteger.valueOf(d2)); - if (w.bitLength() > 31) { - throw new ArithmeticException - ("overflow: numerator too large after multiply"); - } - return new Fraction (w.intValue(), - MathUtils.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 null - * @return a Fraction instance with the resulting values - * @throws IllegalArgumentException if the fraction is null - * @throws ArithmeticException if the resulting numerator or denominator exceeds - * Integer.MAX_VALUE - */ - public Fraction multiply(Fraction fraction) { - if (fraction == null) { - throw new IllegalArgumentException("The fraction must not be null"); - } - 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 = MathUtils.gcd(numerator, fraction.denominator); - int d2 = MathUtils.gcd(fraction.numerator, denominator); - return getReducedFraction - (MathUtils.mulAndCheck(numerator/d1, fraction.numerator/d2), - MathUtils.mulAndCheck(denominator/d2, fraction.denominator/d1)); - } - - /** - *

Divide the value of this fraction by another.

- * - * @param fraction the fraction to divide by, must not be null - * @return a Fraction instance with the resulting values - * @throws IllegalArgumentException if the fraction is null - * @throws ArithmeticException if the fraction to divide by is zero - * @throws ArithmeticException if the resulting numerator or denominator exceeds - * Integer.MAX_VALUE - */ - public Fraction divide(Fraction fraction) { - if (fraction == null) { - throw new IllegalArgumentException("The fraction must not be null"); - } - if (fraction.numerator == 0) { - throw new ArithmeticException("The fraction to divide by must not be zero"); - } - return multiply(fraction.reciprocal()); - } - - /** - *

Creates a Fraction instance with the 2 parts - * of a fraction Y/Z.

- * - *

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 ArithmeticException if the denominator is zero - */ - public static Fraction getReducedFraction(int numerator, int denominator) { - if (denominator == 0) { - throw new ArithmeticException("The denominator must not be zero"); - } - 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 ArithmeticException("overflow: can't negate"); - } - numerator = -numerator; - denominator = -denominator; - } - // simplify fraction. - int gcd = MathUtils.gcd(numerator, denominator); - numerator /= gcd; - denominator /= gcd; - return new Fraction(numerator, denominator); - } - - /** - * Reduce this fraction to lowest terms. This is accomplished by dividing - * both numerator and denominator by their greatest common divisor. - */ - private void reduce() { - // reduce numerator and denominator by greatest common denominator. - int d = MathUtils.gcd(numerator, denominator); - if (d > 1) { - numerator /= d; - denominator /= d; - } - - // move sign to numerator. - if (denominator < 0) { - numerator *= -1; - denominator *= -1; - } - } -} +/* + * 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.math.fraction; + +import java.math.BigInteger; +import org.apache.commons.math.util.MathUtils; + +/** + * Representation of a rational number. + * + * @since 1.1 + * @version $Revision$ $Date$ + */ +public class Fraction extends Number implements Comparable { + + /** A fraction representing "1 / 1". */ + public static final Fraction ONE = new Fraction(1, 1); + + /** A fraction representing "0 / 1". */ + public static final Fraction ZERO = new Fraction(0, 1); + + /** Serializable version identifier */ + private static final long serialVersionUID = 6222990762865980424L; + + + /** The denominator. */ + private int denominator; + + /** The numerator. */ + private 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, 1.0e-5, 100); + } + + /** + * Create a fraction given the double value. + *

+ * References: + *

+ *

+ * @param value the double value to convert to a fraction. + * @param epsilon maximum error allowed. The resulting fraction is within + * epsilon of 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 + { + double r0 = value; + int a0 = (int)Math.floor(r0); + + // check for (almost) integer arguments, which should not go + // to iterations. + if (Math.abs(a0 - value) < epsilon) { + this.numerator = a0; + this.denominator = 1; + return; + } + + int p0 = 1; + int q0 = 0; + int p1 = a0; + int q1 = 1; + + int p2 = 0; + int q2 = 1; + + int n = 0; + boolean stop = false; + do { + ++n; + double r1 = 1.0 / (r0 - a0); + int a1 = (int)Math.floor(r1); + p2 = (a1 * p1) + p0; + q2 = (a1 * q1) + q0; + + double convergent = (double)p2 / (double)q2; + if (n < maxIterations && Math.abs(convergent - value) > epsilon) { + 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); + } + + this.numerator = p2; + this.denominator = q2; + reduce(); + } + + /** + * Create a fraction given the numerator and denominator. The fraction is + * reduced to lowest terms. + * @param num the numerator. + * @param den the denominator. + * @throws ArithmeticException if the denomiator is zero + */ + public Fraction(int num, int den) { + super(); + if (den == 0) { + throw new ArithmeticException("The denominator must not be zero"); + } + if (den < 0) { + if (num == Integer.MIN_VALUE || + den == Integer.MIN_VALUE) { + throw new ArithmeticException("overflow: can't negate"); + } + num = -num; + den = -den; + } + this.numerator = num; + this.denominator = den; + reduce(); + } + + /** + * 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 object, +1 if this is greater + * than object, 0 if they are equal. + */ + public int compareTo(Object object) { + int ret = 0; + + if (this != object) { + Fraction other = (Fraction)object; + double first = doubleValue(); + double second = other.doubleValue(); + + if (first < second) { + ret = -1; + } else if (first > second) { + ret = 1; + } + } + + return ret; + } + + /** + * Gets the fraction as a double. This calculates the fraction as + * the numerator divided by denominator. + * @return the fraction as a double + */ + 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 + * null, not an instance of {@link Fraction}, or not equal + * to this fraction instance. + */ + public boolean equals(Object other) { + boolean ret; + + if (this == other) { + ret = true; + } else if (other == null) { + ret = false; + } else { + try { + // since fractions are always in lowest terms, numerators and + // denominators can be compared directly for equality. + Fraction rhs = (Fraction)other; + ret = (numerator == rhs.numerator) && + (denominator == rhs.denominator); + } catch (ClassCastException ex) { + // ignore exception + ret = false; + } + } + + return ret; + } + + /** + * Gets the fraction as a float. This calculates the fraction as + * the numerator divided by denominator. + * @return the fraction as a float + */ + 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 + */ + public int hashCode() { + return 37 * (37 * 17 + getNumerator()) + getDenominator(); + } + + /** + * Gets the fraction as an int. This returns the whole number part + * of the fraction. + * @return the whole number fraction part + */ + public int intValue() { + return (int)doubleValue(); + } + + /** + * Gets the fraction as a long. This returns the whole number part + * of the fraction. + * @return the whole number fraction part + */ + 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 ArithmeticException("overflow: too large to negate"); + } + 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 null + * @return a Fraction instance with the resulting values + * @throws IllegalArgumentException if the fraction is null + * @throws ArithmeticException if the resulting numerator or denominator exceeds + * Integer.MAX_VALUE + */ + public Fraction add(Fraction fraction) { + return addSub(fraction, true /* add */); + } + + /** + *

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 null + * @return a Fraction instance with the resulting values + * @throws IllegalArgumentException if the fraction is null + * @throws ArithmeticException if the resulting numerator or denominator + * cannot be represented in an int. + */ + public Fraction subtract(Fraction fraction) { + return addSub(fraction, false /* subtract */); + } + + /** + * Implement add and subtract using algorithm described in Knuth 4.5.1. + * + * @param fraction the fraction to subtract, must not be null + * @param isAdd true to add, false to subtract + * @return a Fraction instance with the resulting values + * @throws IllegalArgumentException if the fraction is null + * @throws ArithmeticException if the resulting numerator or denominator + * cannot be represented in an int. + */ + private Fraction addSub(Fraction fraction, boolean isAdd) { + if (fraction == null) { + throw new IllegalArgumentException("The fraction must not be null"); + } + // 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 = MathUtils.gcd(denominator, fraction.denominator); + if (d1==1) { + // result is ( (u*v' +/- u'v) / u'v') + int uvp = MathUtils.mulAndCheck(numerator, fraction.denominator); + int upv = MathUtils.mulAndCheck(fraction.numerator, denominator); + return new Fraction + (isAdd ? MathUtils.addAndCheck(uvp, upv) : + MathUtils.subAndCheck(uvp, upv), + MathUtils.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:MathUtils.gcd(tmodd1, d1); + + // result is (t/d2) / (u'/d1)(v'/d2) + BigInteger w = t.divide(BigInteger.valueOf(d2)); + if (w.bitLength() > 31) { + throw new ArithmeticException + ("overflow: numerator too large after multiply"); + } + return new Fraction (w.intValue(), + MathUtils.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 null + * @return a Fraction instance with the resulting values + * @throws IllegalArgumentException if the fraction is null + * @throws ArithmeticException if the resulting numerator or denominator exceeds + * Integer.MAX_VALUE + */ + public Fraction multiply(Fraction fraction) { + if (fraction == null) { + throw new IllegalArgumentException("The fraction must not be null"); + } + 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 = MathUtils.gcd(numerator, fraction.denominator); + int d2 = MathUtils.gcd(fraction.numerator, denominator); + return getReducedFraction + (MathUtils.mulAndCheck(numerator/d1, fraction.numerator/d2), + MathUtils.mulAndCheck(denominator/d2, fraction.denominator/d1)); + } + + /** + *

Divide the value of this fraction by another.

+ * + * @param fraction the fraction to divide by, must not be null + * @return a Fraction instance with the resulting values + * @throws IllegalArgumentException if the fraction is null + * @throws ArithmeticException if the fraction to divide by is zero + * @throws ArithmeticException if the resulting numerator or denominator exceeds + * Integer.MAX_VALUE + */ + public Fraction divide(Fraction fraction) { + if (fraction == null) { + throw new IllegalArgumentException("The fraction must not be null"); + } + if (fraction.numerator == 0) { + throw new ArithmeticException("The fraction to divide by must not be zero"); + } + return multiply(fraction.reciprocal()); + } + + /** + *

Creates a Fraction instance with the 2 parts + * of a fraction Y/Z.

+ * + *

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 ArithmeticException if the denominator is zero + */ + public static Fraction getReducedFraction(int numerator, int denominator) { + if (denominator == 0) { + throw new ArithmeticException("The denominator must not be zero"); + } + 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 ArithmeticException("overflow: can't negate"); + } + numerator = -numerator; + denominator = -denominator; + } + // simplify fraction. + int gcd = MathUtils.gcd(numerator, denominator); + numerator /= gcd; + denominator /= gcd; + return new Fraction(numerator, denominator); + } + + /** + * Reduce this fraction to lowest terms. This is accomplished by dividing + * both numerator and denominator by their greatest common divisor. + */ + private void reduce() { + // reduce numerator and denominator by greatest common denominator. + int d = MathUtils.gcd(numerator, denominator); + if (d > 1) { + numerator /= d; + denominator /= d; + } + + // move sign to numerator. + if (denominator < 0) { + numerator *= -1; + denominator *= -1; + } + } +} diff --git a/src/java/org/apache/commons/math/fraction/FractionConversionException.java b/src/java/org/apache/commons/math/fraction/FractionConversionException.java new file mode 100644 index 000000000..eff385259 --- /dev/null +++ b/src/java/org/apache/commons/math/fraction/FractionConversionException.java @@ -0,0 +1,22 @@ +package org.apache.commons.math.fraction; + +import org.apache.commons.math.MaxIterationsExceededException; + +public class FractionConversionException extends MaxIterationsExceededException { + + /** Serializable version identifier. */ + private static final long serialVersionUID = 4588659344016668813L; + + /** + * Constructs an exception with specified formatted detail message. + * Message formatting is delegated to {@link java.text.MessageFormat}. + * @param value double value to convert + * @param maxIterations maximal number of iterations allowed + */ + public FractionConversionException(double value, int maxIterations) { + super(maxIterations, + "Unable to convert {0} to fraction after {1} iterations", + new Object[] { new Double(value), new Integer(maxIterations) }); + } + +}