diff options
Diffstat (limited to 'src/main/java/org/apache/commons/math3/fraction')
11 files changed, 3226 insertions, 0 deletions
diff --git a/src/main/java/org/apache/commons/math3/fraction/AbstractFormat.java b/src/main/java/org/apache/commons/math3/fraction/AbstractFormat.java new file mode 100644 index 0000000..c17d127 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/fraction/AbstractFormat.java @@ -0,0 +1,216 @@ +/* + * 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.exception.NullArgumentException; +import org.apache.commons.math3.exception.util.LocalizedFormats; + +import java.io.Serializable; +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +/** + * Common part shared by both {@link FractionFormat} and {@link BigFractionFormat}. + * + * @since 2.0 + */ +public abstract class AbstractFormat extends NumberFormat implements Serializable { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -6981118387974191891L; + + /** The format used for the denominator. */ + private NumberFormat denominatorFormat; + + /** The format used for the numerator. */ + private NumberFormat numeratorFormat; + + /** + * Create an improper formatting instance with the default number format for the numerator and + * denominator. + */ + protected AbstractFormat() { + this(getDefaultNumberFormat()); + } + + /** + * Create an improper formatting instance with a custom number format for both the numerator and + * denominator. + * + * @param format the custom format for both the numerator and denominator. + */ + protected AbstractFormat(final NumberFormat format) { + this(format, (NumberFormat) format.clone()); + } + + /** + * Create an improper formatting instance with a custom number format for the numerator and a + * custom number format for the denominator. + * + * @param numeratorFormat the custom format for the numerator. + * @param denominatorFormat the custom format for the denominator. + */ + protected AbstractFormat( + final NumberFormat numeratorFormat, final NumberFormat denominatorFormat) { + this.numeratorFormat = numeratorFormat; + this.denominatorFormat = denominatorFormat; + } + + /** + * Create a default number format. The default number format is based on {@link + * NumberFormat#getNumberInstance(java.util.Locale)}. The only customization is the maximum + * number of BigFraction digits, which is set to 0. + * + * @return the default number format. + */ + protected static NumberFormat getDefaultNumberFormat() { + return getDefaultNumberFormat(Locale.getDefault()); + } + + /** + * Create a default number format. The default number format is based on {@link + * NumberFormat#getNumberInstance(java.util.Locale)}. The only customization is the maximum + * number of BigFraction digits, which is set to 0. + * + * @param locale the specific locale used by the format. + * @return the default number format specific to the given locale. + */ + protected static NumberFormat getDefaultNumberFormat(final Locale locale) { + final NumberFormat nf = NumberFormat.getNumberInstance(locale); + nf.setMaximumFractionDigits(0); + nf.setParseIntegerOnly(true); + return nf; + } + + /** + * Access the denominator format. + * + * @return the denominator format. + */ + public NumberFormat getDenominatorFormat() { + return denominatorFormat; + } + + /** + * Access the numerator format. + * + * @return the numerator format. + */ + public NumberFormat getNumeratorFormat() { + return numeratorFormat; + } + + /** + * Modify the denominator format. + * + * @param format the new denominator format value. + * @throws NullArgumentException if {@code format} is {@code null}. + */ + public void setDenominatorFormat(final NumberFormat format) { + if (format == null) { + throw new NullArgumentException(LocalizedFormats.DENOMINATOR_FORMAT); + } + this.denominatorFormat = format; + } + + /** + * Modify the numerator format. + * + * @param format the new numerator format value. + * @throws NullArgumentException if {@code format} is {@code null}. + */ + public void setNumeratorFormat(final NumberFormat format) { + if (format == null) { + throw new NullArgumentException(LocalizedFormats.NUMERATOR_FORMAT); + } + this.numeratorFormat = format; + } + + /** + * Parses <code>source</code> until a non-whitespace character is found. + * + * @param source the string to parse + * @param pos input/output parsing parameter. On output, <code>pos</code> holds the index of the + * next non-whitespace character. + */ + protected static void parseAndIgnoreWhitespace(final String source, final ParsePosition pos) { + parseNextCharacter(source, pos); + pos.setIndex(pos.getIndex() - 1); + } + + /** + * Parses <code>source</code> until a non-whitespace character is found. + * + * @param source the string to parse + * @param pos input/output parsing parameter. + * @return the first non-whitespace character. + */ + protected static char parseNextCharacter(final String source, final ParsePosition pos) { + int index = pos.getIndex(); + final int n = source.length(); + char ret = 0; + + if (index < n) { + char c; + do { + c = source.charAt(index++); + } while (Character.isWhitespace(c) && index < n); + pos.setIndex(index); + + if (index < n) { + ret = c; + } + } + + return ret; + } + + /** + * Formats a double value as a fraction and appends the result to a StringBuffer. + * + * @param value the double value to format + * @param buffer StringBuffer to append to + * @param position On input: an alignment field, if desired. On output: the offsets of the + * alignment field + * @return a reference to the appended buffer + * @see #format(Object, StringBuffer, FieldPosition) + */ + @Override + public StringBuffer format( + final double value, final StringBuffer buffer, final FieldPosition position) { + return format(Double.valueOf(value), buffer, position); + } + + /** + * Formats a long value as a fraction and appends the result to a StringBuffer. + * + * @param value the long value to format + * @param buffer StringBuffer to append to + * @param position On input: an alignment field, if desired. On output: the offsets of the + * alignment field + * @return a reference to the appended buffer + * @see #format(Object, StringBuffer, FieldPosition) + */ + @Override + public StringBuffer format( + final long value, final StringBuffer buffer, final FieldPosition position) { + return format(Long.valueOf(value), buffer, position); + } +} diff --git a/src/main/java/org/apache/commons/math3/fraction/BigFraction.java b/src/main/java/org/apache/commons/math3/fraction/BigFraction.java new file mode 100644 index 0000000..56616b3 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/fraction/BigFraction.java @@ -0,0 +1,1065 @@ +/* + * 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.MathIllegalArgumentException; +import org.apache.commons.math3.exception.NullArgumentException; +import org.apache.commons.math3.exception.ZeroException; +import org.apache.commons.math3.exception.util.LocalizedFormats; +import org.apache.commons.math3.util.ArithmeticUtils; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.MathUtils; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * Representation of a rational number without any overflow. This class is immutable. + * + * @since 2.0 + */ +public class BigFraction extends Number + implements FieldElement<BigFraction>, Comparable<BigFraction>, Serializable { + + /** A fraction representing "2 / 1". */ + public static final BigFraction TWO = new BigFraction(2); + + /** A fraction representing "1". */ + public static final BigFraction ONE = new BigFraction(1); + + /** A fraction representing "0". */ + public static final BigFraction ZERO = new BigFraction(0); + + /** A fraction representing "-1 / 1". */ + public static final BigFraction MINUS_ONE = new BigFraction(-1); + + /** A fraction representing "4/5". */ + public static final BigFraction FOUR_FIFTHS = new BigFraction(4, 5); + + /** A fraction representing "1/5". */ + public static final BigFraction ONE_FIFTH = new BigFraction(1, 5); + + /** A fraction representing "1/2". */ + public static final BigFraction ONE_HALF = new BigFraction(1, 2); + + /** A fraction representing "1/4". */ + public static final BigFraction ONE_QUARTER = new BigFraction(1, 4); + + /** A fraction representing "1/3". */ + public static final BigFraction ONE_THIRD = new BigFraction(1, 3); + + /** A fraction representing "3/5". */ + public static final BigFraction THREE_FIFTHS = new BigFraction(3, 5); + + /** A fraction representing "3/4". */ + public static final BigFraction THREE_QUARTERS = new BigFraction(3, 4); + + /** A fraction representing "2/5". */ + public static final BigFraction TWO_FIFTHS = new BigFraction(2, 5); + + /** A fraction representing "2/4". */ + public static final BigFraction TWO_QUARTERS = new BigFraction(2, 4); + + /** A fraction representing "2/3". */ + public static final BigFraction TWO_THIRDS = new BigFraction(2, 3); + + /** Serializable version identifier. */ + private static final long serialVersionUID = -5630213147331578515L; + + /** <code>BigInteger</code> representation of 100. */ + private static final BigInteger ONE_HUNDRED = BigInteger.valueOf(100); + + /** The numerator. */ + private final BigInteger numerator; + + /** The denominator. */ + private final BigInteger denominator; + + /** + * Create a {@link BigFraction} equivalent to the passed {@code BigInteger}, ie "num / 1". + * + * @param num the numerator. + */ + public BigFraction(final BigInteger num) { + this(num, BigInteger.ONE); + } + + /** + * Create a {@link BigFraction} given the numerator and denominator as {@code BigInteger}. The + * {@link BigFraction} is reduced to lowest terms. + * + * @param num the numerator, must not be {@code null}. + * @param den the denominator, must not be {@code null}. + * @throws ZeroException if the denominator is zero. + * @throws NullArgumentException if either of the arguments is null + */ + public BigFraction(BigInteger num, BigInteger den) { + MathUtils.checkNotNull(num, LocalizedFormats.NUMERATOR); + MathUtils.checkNotNull(den, LocalizedFormats.DENOMINATOR); + if (den.signum() == 0) { + throw new ZeroException(LocalizedFormats.ZERO_DENOMINATOR); + } + if (num.signum() == 0) { + numerator = BigInteger.ZERO; + denominator = BigInteger.ONE; + } else { + + // reduce numerator and denominator by greatest common denominator + final BigInteger gcd = num.gcd(den); + if (BigInteger.ONE.compareTo(gcd) < 0) { + num = num.divide(gcd); + den = den.divide(gcd); + } + + // move sign to numerator + if (den.signum() == -1) { + num = num.negate(); + den = den.negate(); + } + + // store the values in the final fields + numerator = num; + denominator = den; + } + } + + /** + * Create a fraction given the double value. + * + * <p>This constructor behaves <em>differently</em> from {@link #BigFraction(double, double, + * int)}. It converts the double value exactly, considering its internal bits representation. + * This works for all values except NaN and infinities and does not requires any loop or + * convergence threshold. + * + * <p>Since this conversion is exact and since double numbers are sometimes approximated, the + * fraction created may seem strange in some cases. For example, calling <code> + * new BigFraction(1.0 / 3.0)</code> does <em>not</em> create the fraction 1/3, but the fraction + * 6004799503160661 / 18014398509481984 because the double number passed to the constructor is + * not exactly 1/3 (this number cannot be stored exactly in IEEE754). + * + * @see #BigFraction(double, double, int) + * @param value the double value to convert to a fraction. + * @exception MathIllegalArgumentException if value is NaN or infinite + */ + public BigFraction(final double value) throws MathIllegalArgumentException { + if (Double.isNaN(value)) { + throw new MathIllegalArgumentException(LocalizedFormats.NAN_VALUE_CONVERSION); + } + if (Double.isInfinite(value)) { + throw new MathIllegalArgumentException(LocalizedFormats.INFINITE_VALUE_CONVERSION); + } + + // compute m and k such that value = m * 2^k + final long bits = Double.doubleToLongBits(value); + final long sign = bits & 0x8000000000000000L; + final long exponent = bits & 0x7ff0000000000000L; + long m = bits & 0x000fffffffffffffL; + if (exponent != 0) { + // this was a normalized number, add the implicit most significant bit + m |= 0x0010000000000000L; + } + if (sign != 0) { + m = -m; + } + int k = ((int) (exponent >> 52)) - 1075; + while (((m & 0x001ffffffffffffeL) != 0) && ((m & 0x1) == 0)) { + m >>= 1; + ++k; + } + + if (k < 0) { + numerator = BigInteger.valueOf(m); + denominator = BigInteger.ZERO.flipBit(-k); + } else { + numerator = BigInteger.valueOf(m).multiply(BigInteger.ZERO.flipBit(k)); + denominator = BigInteger.ONE; + } + } + + /** + * 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</code> + * of <code>value</code>, in absolute terms. + * @param maxIterations maximum number of convergents. + * @throws FractionConversionException if the continued fraction failed to converge. + * @see #BigFraction(double) + */ + public BigFraction(final double value, final double epsilon, final int maxIterations) + throws FractionConversionException { + this(value, epsilon, Integer.MAX_VALUE, maxIterations); + } + + /** + * 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</code> + * of <code>value</code>, 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 BigFraction( + final double value, final double epsilon, final 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) { + numerator = BigInteger.valueOf(a0); + denominator = BigInteger.ONE; + 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; + final double r1 = 1.0 / (r0 - a0); + final long a1 = (long) FastMath.floor(r1); + p2 = (a1 * p1) + p0; + q2 = (a1 * q1) + q0; + if ((p2 > overflow) || (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); + } + + final 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) { + numerator = BigInteger.valueOf(p2); + denominator = BigInteger.valueOf(q2); + } else { + numerator = BigInteger.valueOf(p1); + denominator = BigInteger.valueOf(q1); + } + } + + /** + * 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 BigFraction(final double value, final int maxDenominator) + throws FractionConversionException { + this(value, 0, maxDenominator, 100); + } + + /** + * Create a {@link BigFraction} equivalent to the passed {@code int}, ie "num / 1". + * + * @param num the numerator. + */ + public BigFraction(final int num) { + this(BigInteger.valueOf(num), BigInteger.ONE); + } + + /** + * Create a {@link BigFraction} given the numerator and denominator as simple {@code int}. The + * {@link BigFraction} is reduced to lowest terms. + * + * @param num the numerator. + * @param den the denominator. + */ + public BigFraction(final int num, final int den) { + this(BigInteger.valueOf(num), BigInteger.valueOf(den)); + } + + /** + * Create a {@link BigFraction} equivalent to the passed long, ie "num / 1". + * + * @param num the numerator. + */ + public BigFraction(final long num) { + this(BigInteger.valueOf(num), BigInteger.ONE); + } + + /** + * Create a {@link BigFraction} given the numerator and denominator as simple {@code long}. The + * {@link BigFraction} is reduced to lowest terms. + * + * @param num the numerator. + * @param den the denominator. + */ + public BigFraction(final long num, final long den) { + this(BigInteger.valueOf(num), BigInteger.valueOf(den)); + } + + /** + * Creates a <code>BigFraction</code> 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 ArithmeticException if the denominator is <code>zero</code>. + */ + public static BigFraction getReducedFraction(final int numerator, final int denominator) { + if (numerator == 0) { + return ZERO; // normalize zero. + } + + return new BigFraction(numerator, denominator); + } + + /** + * Returns the absolute value of this {@link BigFraction}. + * + * @return the absolute value as a {@link BigFraction}. + */ + public BigFraction abs() { + return (numerator.signum() == 1) ? this : negate(); + } + + /** + * Adds the value of this fraction to the passed {@link BigInteger}, returning the result in + * reduced form. + * + * @param bg the {@link BigInteger} to add, must'nt be <code>null</code>. + * @return a <code>BigFraction</code> instance with the resulting values. + * @throws NullArgumentException if the {@link BigInteger} is <code>null</code>. + */ + public BigFraction add(final BigInteger bg) throws NullArgumentException { + MathUtils.checkNotNull(bg); + + if (numerator.signum() == 0) { + return new BigFraction(bg); + } + if (bg.signum() == 0) { + return this; + } + + return new BigFraction(numerator.add(denominator.multiply(bg)), denominator); + } + + /** + * Adds the value of this fraction to the passed {@code integer}, returning the result in + * reduced form. + * + * @param i the {@code integer} to add. + * @return a <code>BigFraction</code> instance with the resulting values. + */ + public BigFraction add(final int i) { + return add(BigInteger.valueOf(i)); + } + + /** + * Adds the value of this fraction to the passed {@code long}, returning the result in reduced + * form. + * + * @param l the {@code long} to add. + * @return a <code>BigFraction</code> instance with the resulting values. + */ + public BigFraction add(final long l) { + return add(BigInteger.valueOf(l)); + } + + /** + * Adds the value of this fraction to another, returning the result in reduced form. + * + * @param fraction the {@link BigFraction} to add, must not be <code>null</code>. + * @return a {@link BigFraction} instance with the resulting values. + * @throws NullArgumentException if the {@link BigFraction} is {@code null}. + */ + public BigFraction add(final BigFraction fraction) { + if (fraction == null) { + throw new NullArgumentException(LocalizedFormats.FRACTION); + } + if (fraction.numerator.signum() == 0) { + return this; + } + if (numerator.signum() == 0) { + return fraction; + } + + BigInteger num = null; + BigInteger den = null; + + if (denominator.equals(fraction.denominator)) { + num = numerator.add(fraction.numerator); + den = denominator; + } else { + num = + (numerator.multiply(fraction.denominator)) + .add((fraction.numerator).multiply(denominator)); + den = denominator.multiply(fraction.denominator); + } + + if (num.signum() == 0) { + return ZERO; + } + + return new BigFraction(num, den); + } + + /** + * Gets the fraction as a <code>BigDecimal</code>. This calculates the fraction as the numerator + * divided by denominator. + * + * @return the fraction as a <code>BigDecimal</code>. + * @throws ArithmeticException if the exact quotient does not have a terminating decimal + * expansion. + * @see BigDecimal + */ + public BigDecimal bigDecimalValue() { + return new BigDecimal(numerator).divide(new BigDecimal(denominator)); + } + + /** + * Gets the fraction as a <code>BigDecimal</code> following the passed rounding mode. This + * calculates the fraction as the numerator divided by denominator. + * + * @param roundingMode rounding mode to apply. see {@link BigDecimal} constants. + * @return the fraction as a <code>BigDecimal</code>. + * @throws IllegalArgumentException if {@code roundingMode} does not represent a valid rounding + * mode. + * @see BigDecimal + */ + public BigDecimal bigDecimalValue(final int roundingMode) { + return new BigDecimal(numerator).divide(new BigDecimal(denominator), roundingMode); + } + + /** + * Gets the fraction as a <code>BigDecimal</code> following the passed scale and rounding mode. + * This calculates the fraction as the numerator divided by denominator. + * + * @param scale scale of the <code>BigDecimal</code> quotient to be returned. see {@link + * BigDecimal} for more information. + * @param roundingMode rounding mode to apply. see {@link BigDecimal} constants. + * @return the fraction as a <code>BigDecimal</code>. + * @see BigDecimal + */ + public BigDecimal bigDecimalValue(final int scale, final int roundingMode) { + return new BigDecimal(numerator).divide(new BigDecimal(denominator), scale, roundingMode); + } + + /** + * Compares this object to another based on size. + * + * @param object the object to compare to, must not be <code>null</code>. + * @return -1 if this is less than {@code object}, +1 if this is greater than {@code object}, 0 + * if they are equal. + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(final BigFraction object) { + int lhsSigNum = numerator.signum(); + int rhsSigNum = object.numerator.signum(); + + if (lhsSigNum != rhsSigNum) { + return (lhsSigNum > rhsSigNum) ? 1 : -1; + } + if (lhsSigNum == 0) { + return 0; + } + + BigInteger nOd = numerator.multiply(object.denominator); + BigInteger dOn = denominator.multiply(object.numerator); + return nOd.compareTo(dOn); + } + + /** + * Divide the value of this fraction by the passed {@code BigInteger}, ie {@code this * 1 / bg}, + * returning the result in reduced form. + * + * @param bg the {@code BigInteger} to divide by, must not be {@code null} + * @return a {@link BigFraction} instance with the resulting values + * @throws NullArgumentException if the {@code BigInteger} is {@code null} + * @throws MathArithmeticException if the fraction to divide by is zero + */ + public BigFraction divide(final BigInteger bg) { + if (bg == null) { + throw new NullArgumentException(LocalizedFormats.FRACTION); + } + if (bg.signum() == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR); + } + if (numerator.signum() == 0) { + return ZERO; + } + return new BigFraction(numerator, denominator.multiply(bg)); + } + + /** + * Divide the value of this fraction by the passed {@code int}, ie {@code this * 1 / i}, + * returning the result in reduced form. + * + * @param i the {@code int} to divide by + * @return a {@link BigFraction} instance with the resulting values + * @throws MathArithmeticException if the fraction to divide by is zero + */ + public BigFraction divide(final int i) { + return divide(BigInteger.valueOf(i)); + } + + /** + * Divide the value of this fraction by the passed {@code long}, ie {@code this * 1 / l}, + * returning the result in reduced form. + * + * @param l the {@code long} to divide by + * @return a {@link BigFraction} instance with the resulting values + * @throws MathArithmeticException if the fraction to divide by is zero + */ + public BigFraction divide(final long l) { + return divide(BigInteger.valueOf(l)); + } + + /** + * Divide the value of this fraction by another, returning the result in reduced form. + * + * @param fraction Fraction to divide by, must not be {@code null}. + * @return a {@link BigFraction} instance with the resulting values. + * @throws NullArgumentException if the {@code fraction} is {@code null}. + * @throws MathArithmeticException if the fraction to divide by is zero + */ + public BigFraction divide(final BigFraction fraction) { + if (fraction == null) { + throw new NullArgumentException(LocalizedFormats.FRACTION); + } + if (fraction.numerator.signum() == 0) { + throw new MathArithmeticException(LocalizedFormats.ZERO_DENOMINATOR); + } + if (numerator.signum() == 0) { + return ZERO; + } + + return multiply(fraction.reciprocal()); + } + + /** + * Gets the fraction as a {@code double}. This calculates the fraction as the numerator divided + * by denominator. + * + * @return the fraction as a {@code double} + * @see java.lang.Number#doubleValue() + */ + @Override + public double doubleValue() { + double result = numerator.doubleValue() / denominator.doubleValue(); + if (Double.isNaN(result)) { + // Numerator and/or denominator must be out of range: + // Calculate how far to shift them to put them in range. + int shift = + FastMath.max(numerator.bitLength(), denominator.bitLength()) + - FastMath.getExponent(Double.MAX_VALUE); + result = + numerator.shiftRight(shift).doubleValue() + / denominator.shiftRight(shift).doubleValue(); + } + return result; + } + + /** + * 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, can be <code>null</code>. + * @return true if two fractions are equal, false if object is <code>null</code>, not an + * instance of {@link BigFraction}, or not equal to this fraction instance. + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(final Object other) { + boolean ret = false; + + if (this == other) { + ret = true; + } else if (other instanceof BigFraction) { + BigFraction rhs = ((BigFraction) other).reduce(); + BigFraction thisOne = this.reduce(); + ret = + thisOne.numerator.equals(rhs.numerator) + && thisOne.denominator.equals(rhs.denominator); + } + + return ret; + } + + /** + * Gets the fraction as a {@code float}. This calculates the fraction as the numerator divided + * by denominator. + * + * @return the fraction as a {@code float}. + * @see java.lang.Number#floatValue() + */ + @Override + public float floatValue() { + float result = numerator.floatValue() / denominator.floatValue(); + if (Double.isNaN(result)) { + // Numerator and/or denominator must be out of range: + // Calculate how far to shift them to put them in range. + int shift = + FastMath.max(numerator.bitLength(), denominator.bitLength()) + - FastMath.getExponent(Float.MAX_VALUE); + result = + numerator.shiftRight(shift).floatValue() + / denominator.shiftRight(shift).floatValue(); + } + return result; + } + + /** + * Access the denominator as a <code>BigInteger</code>. + * + * @return the denominator as a <code>BigInteger</code>. + */ + public BigInteger getDenominator() { + return denominator; + } + + /** + * Access the denominator as a {@code int}. + * + * @return the denominator as a {@code int}. + */ + public int getDenominatorAsInt() { + return denominator.intValue(); + } + + /** + * Access the denominator as a {@code long}. + * + * @return the denominator as a {@code long}. + */ + public long getDenominatorAsLong() { + return denominator.longValue(); + } + + /** + * Access the numerator as a <code>BigInteger</code>. + * + * @return the numerator as a <code>BigInteger</code>. + */ + public BigInteger getNumerator() { + return numerator; + } + + /** + * Access the numerator as a {@code int}. + * + * @return the numerator as a {@code int}. + */ + public int getNumeratorAsInt() { + return numerator.intValue(); + } + + /** + * Access the numerator as a {@code long}. + * + * @return the numerator as a {@code long}. + */ + public long getNumeratorAsLong() { + return numerator.longValue(); + } + + /** + * Gets a hashCode for the fraction. + * + * @return a hash code value for this object. + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return 37 * (37 * 17 + numerator.hashCode()) + denominator.hashCode(); + } + + /** + * Gets the fraction as an {@code int}. This returns the whole number part of the fraction. + * + * @return the whole number fraction part. + * @see java.lang.Number#intValue() + */ + @Override + public int intValue() { + return numerator.divide(denominator).intValue(); + } + + /** + * Gets the fraction as a {@code long}. This returns the whole number part of the fraction. + * + * @return the whole number fraction part. + * @see java.lang.Number#longValue() + */ + @Override + public long longValue() { + return numerator.divide(denominator).longValue(); + } + + /** + * Multiplies the value of this fraction by the passed <code>BigInteger</code>, returning the + * result in reduced form. + * + * @param bg the {@code BigInteger} to multiply by. + * @return a {@code BigFraction} instance with the resulting values. + * @throws NullArgumentException if {@code bg} is {@code null}. + */ + public BigFraction multiply(final BigInteger bg) { + if (bg == null) { + throw new NullArgumentException(); + } + if (numerator.signum() == 0 || bg.signum() == 0) { + return ZERO; + } + return new BigFraction(bg.multiply(numerator), denominator); + } + + /** + * Multiply the value of this fraction by the passed {@code int}, returning the result in + * reduced form. + * + * @param i the {@code int} to multiply by. + * @return a {@link BigFraction} instance with the resulting values. + */ + public BigFraction multiply(final int i) { + if (i == 0 || numerator.signum() == 0) { + return ZERO; + } + + return multiply(BigInteger.valueOf(i)); + } + + /** + * Multiply the value of this fraction by the passed {@code long}, returning the result in + * reduced form. + * + * @param l the {@code long} to multiply by. + * @return a {@link BigFraction} instance with the resulting values. + */ + public BigFraction multiply(final long l) { + if (l == 0 || numerator.signum() == 0) { + return ZERO; + } + + return multiply(BigInteger.valueOf(l)); + } + + /** + * Multiplies the value of this fraction by another, returning the result in reduced form. + * + * @param fraction Fraction to multiply by, must not be {@code null}. + * @return a {@link BigFraction} instance with the resulting values. + * @throws NullArgumentException if {@code fraction} is {@code null}. + */ + public BigFraction multiply(final BigFraction fraction) { + if (fraction == null) { + throw new NullArgumentException(LocalizedFormats.FRACTION); + } + if (numerator.signum() == 0 || fraction.numerator.signum() == 0) { + return ZERO; + } + return new BigFraction( + numerator.multiply(fraction.numerator), denominator.multiply(fraction.denominator)); + } + + /** + * Return the additive inverse of this fraction, returning the result in reduced form. + * + * @return the negation of this fraction. + */ + public BigFraction negate() { + return new BigFraction(numerator.negate(), denominator); + } + + /** + * 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 multiply(ONE_HUNDRED).doubleValue(); + } + + /** + * Returns a {@code BigFraction} whose value is {@code (this<sup>exponent</sup>)}, returning the + * result in reduced form. + * + * @param exponent exponent to which this {@code BigFraction} is to be raised. + * @return <tt>this<sup>exponent</sup></tt>. + */ + public BigFraction pow(final int exponent) { + if (exponent == 0) { + return ONE; + } + if (numerator.signum() == 0) { + return this; + } + + if (exponent < 0) { + return new BigFraction(denominator.pow(-exponent), numerator.pow(-exponent)); + } + return new BigFraction(numerator.pow(exponent), denominator.pow(exponent)); + } + + /** + * Returns a <code>BigFraction</code> whose value is <tt>(this<sup>exponent</sup>)</tt>, + * returning the result in reduced form. + * + * @param exponent exponent to which this <code>BigFraction</code> is to be raised. + * @return <tt>this<sup>exponent</sup></tt> as a <code>BigFraction</code>. + */ + public BigFraction pow(final long exponent) { + if (exponent == 0) { + return ONE; + } + if (numerator.signum() == 0) { + return this; + } + + if (exponent < 0) { + return new BigFraction( + ArithmeticUtils.pow(denominator, -exponent), + ArithmeticUtils.pow(numerator, -exponent)); + } + return new BigFraction( + ArithmeticUtils.pow(numerator, exponent), + ArithmeticUtils.pow(denominator, exponent)); + } + + /** + * Returns a <code>BigFraction</code> whose value is <tt>(this<sup>exponent</sup>)</tt>, + * returning the result in reduced form. + * + * @param exponent exponent to which this <code>BigFraction</code> is to be raised. + * @return <tt>this<sup>exponent</sup></tt> as a <code>BigFraction</code>. + */ + public BigFraction pow(final BigInteger exponent) { + if (exponent.signum() == 0) { + return ONE; + } + if (numerator.signum() == 0) { + return this; + } + + if (exponent.signum() == -1) { + final BigInteger eNeg = exponent.negate(); + return new BigFraction( + ArithmeticUtils.pow(denominator, eNeg), ArithmeticUtils.pow(numerator, eNeg)); + } + return new BigFraction( + ArithmeticUtils.pow(numerator, exponent), + ArithmeticUtils.pow(denominator, exponent)); + } + + /** + * Returns a <code>double</code> whose value is <tt>(this<sup>exponent</sup>)</tt>, returning + * the result in reduced form. + * + * @param exponent exponent to which this <code>BigFraction</code> is to be raised. + * @return <tt>this<sup>exponent</sup></tt>. + */ + public double pow(final double exponent) { + return FastMath.pow(numerator.doubleValue(), exponent) + / FastMath.pow(denominator.doubleValue(), exponent); + } + + /** + * Return the multiplicative inverse of this fraction. + * + * @return the reciprocal fraction. + */ + public BigFraction reciprocal() { + return new BigFraction(denominator, numerator); + } + + /** + * Reduce this <code>BigFraction</code> to its lowest terms. + * + * @return the reduced <code>BigFraction</code>. It doesn't change anything if the fraction can + * be reduced. + */ + public BigFraction reduce() { + final BigInteger gcd = numerator.gcd(denominator); + + if (BigInteger.ONE.compareTo(gcd) < 0) { + return new BigFraction(numerator.divide(gcd), denominator.divide(gcd)); + } else { + return this; + } + } + + /** + * Subtracts the value of an {@link BigInteger} from the value of this {@code BigFraction}, + * returning the result in reduced form. + * + * @param bg the {@link BigInteger} to subtract, cannot be {@code null}. + * @return a {@code BigFraction} instance with the resulting values. + * @throws NullArgumentException if the {@link BigInteger} is {@code null}. + */ + public BigFraction subtract(final BigInteger bg) { + if (bg == null) { + throw new NullArgumentException(); + } + if (bg.signum() == 0) { + return this; + } + if (numerator.signum() == 0) { + return new BigFraction(bg.negate()); + } + + return new BigFraction(numerator.subtract(denominator.multiply(bg)), denominator); + } + + /** + * Subtracts the value of an {@code integer} from the value of this {@code BigFraction}, + * returning the result in reduced form. + * + * @param i the {@code integer} to subtract. + * @return a {@code BigFraction} instance with the resulting values. + */ + public BigFraction subtract(final int i) { + return subtract(BigInteger.valueOf(i)); + } + + /** + * Subtracts the value of a {@code long} from the value of this {@code BigFraction}, returning + * the result in reduced form. + * + * @param l the {@code long} to subtract. + * @return a {@code BigFraction} instance with the resulting values. + */ + public BigFraction subtract(final long l) { + return subtract(BigInteger.valueOf(l)); + } + + /** + * Subtracts the value of another fraction from the value of this one, returning the result in + * reduced form. + * + * @param fraction {@link BigFraction} to subtract, must not be {@code null}. + * @return a {@link BigFraction} instance with the resulting values + * @throws NullArgumentException if the {@code fraction} is {@code null}. + */ + public BigFraction subtract(final BigFraction fraction) { + if (fraction == null) { + throw new NullArgumentException(LocalizedFormats.FRACTION); + } + if (fraction.numerator.signum() == 0) { + return this; + } + if (numerator.signum() == 0) { + return fraction.negate(); + } + + BigInteger num = null; + BigInteger den = null; + if (denominator.equals(fraction.denominator)) { + num = numerator.subtract(fraction.numerator); + den = denominator; + } else { + num = + (numerator.multiply(fraction.denominator)) + .subtract((fraction.numerator).multiply(denominator)); + den = denominator.multiply(fraction.denominator); + } + return new BigFraction(num, den); + } + + /** + * Returns the <code>String</code> 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 (BigInteger.ONE.equals(denominator)) { + str = numerator.toString(); + } else if (BigInteger.ZERO.equals(numerator)) { + str = "0"; + } else { + str = numerator + " / " + denominator; + } + return str; + } + + /** {@inheritDoc} */ + public BigFractionField getField() { + return BigFractionField.getInstance(); + } +} diff --git a/src/main/java/org/apache/commons/math3/fraction/BigFractionField.java b/src/main/java/org/apache/commons/math3/fraction/BigFractionField.java new file mode 100644 index 0000000..763cea1 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/fraction/BigFractionField.java @@ -0,0 +1,87 @@ +/* + * 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.Field; +import org.apache.commons.math3.FieldElement; + +import java.io.Serializable; + +/** + * Representation of the fractional numbers without any overflow field. + * + * <p>This class is a singleton. + * + * @see Fraction + * @since 2.0 + */ +public class BigFractionField implements Field<BigFraction>, Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -1699294557189741703L; + + /** Private constructor for the singleton. */ + private BigFractionField() {} + + /** + * Get the unique instance. + * + * @return the unique instance + */ + public static BigFractionField getInstance() { + return LazyHolder.INSTANCE; + } + + /** {@inheritDoc} */ + public BigFraction getOne() { + return BigFraction.ONE; + } + + /** {@inheritDoc} */ + public BigFraction getZero() { + return BigFraction.ZERO; + } + + /** {@inheritDoc} */ + public Class<? extends FieldElement<BigFraction>> getRuntimeClass() { + return BigFraction.class; + } + + // CHECKSTYLE: stop HideUtilityClassConstructor + /** + * Holder for the instance. + * + * <p>We use here the Initialization On Demand Holder Idiom. + */ + private static class LazyHolder { + /** Cached field instance. */ + private static final BigFractionField INSTANCE = new BigFractionField(); + } + + // CHECKSTYLE: resume HideUtilityClassConstructor + + /** + * Handle deserialization of the singleton. + * + * @return the singleton instance + */ + private Object readResolve() { + // return the singleton instance + return LazyHolder.INSTANCE; + } +} diff --git a/src/main/java/org/apache/commons/math3/fraction/BigFractionFormat.java b/src/main/java/org/apache/commons/math3/fraction/BigFractionFormat.java new file mode 100644 index 0000000..aed9b26 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/fraction/BigFractionFormat.java @@ -0,0 +1,288 @@ +/* + * 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.exception.MathIllegalArgumentException; +import org.apache.commons.math3.exception.MathParseException; +import org.apache.commons.math3.exception.util.LocalizedFormats; + +import java.io.Serializable; +import java.math.BigInteger; +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +/** + * Formats a BigFraction number in proper format or improper format. + * + * <p>The number format for each of the whole number, numerator and, denominator can be configured. + * + * @since 2.0 + */ +public class BigFractionFormat extends AbstractFormat implements Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -2932167925527338976L; + + /** + * Create an improper formatting instance with the default number format for the numerator and + * denominator. + */ + public BigFractionFormat() {} + + /** + * Create an improper formatting instance with a custom number format for both the numerator and + * denominator. + * + * @param format the custom format for both the numerator and denominator. + */ + public BigFractionFormat(final NumberFormat format) { + super(format); + } + + /** + * Create an improper formatting instance with a custom number format for the numerator and a + * custom number format for the denominator. + * + * @param numeratorFormat the custom format for the numerator. + * @param denominatorFormat the custom format for the denominator. + */ + public BigFractionFormat( + final NumberFormat numeratorFormat, final NumberFormat denominatorFormat) { + super(numeratorFormat, denominatorFormat); + } + + /** + * Get the set of locales for which complex formats are available. This is the same set as the + * {@link NumberFormat} set. + * + * @return available complex format locales. + */ + public static Locale[] getAvailableLocales() { + return NumberFormat.getAvailableLocales(); + } + + /** + * This static method calls formatBigFraction() on a default instance of BigFractionFormat. + * + * @param f BigFraction object to format + * @return A formatted BigFraction in proper form. + */ + public static String formatBigFraction(final BigFraction f) { + return getImproperInstance().format(f); + } + + /** + * Returns the default complex format for the current locale. + * + * @return the default complex format. + */ + public static BigFractionFormat getImproperInstance() { + return getImproperInstance(Locale.getDefault()); + } + + /** + * Returns the default complex format for the given locale. + * + * @param locale the specific locale used by the format. + * @return the complex format specific to the given locale. + */ + public static BigFractionFormat getImproperInstance(final Locale locale) { + return new BigFractionFormat(getDefaultNumberFormat(locale)); + } + + /** + * Returns the default complex format for the current locale. + * + * @return the default complex format. + */ + public static BigFractionFormat getProperInstance() { + return getProperInstance(Locale.getDefault()); + } + + /** + * Returns the default complex format for the given locale. + * + * @param locale the specific locale used by the format. + * @return the complex format specific to the given locale. + */ + public static BigFractionFormat getProperInstance(final Locale locale) { + return new ProperBigFractionFormat(getDefaultNumberFormat(locale)); + } + + /** + * Formats a {@link BigFraction} object to produce a string. The BigFraction is output in + * improper format. + * + * @param BigFraction the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment + * field + * @return the value passed in as toAppendTo. + */ + public StringBuffer format( + final BigFraction BigFraction, final StringBuffer toAppendTo, final FieldPosition pos) { + + pos.setBeginIndex(0); + pos.setEndIndex(0); + + getNumeratorFormat().format(BigFraction.getNumerator(), toAppendTo, pos); + toAppendTo.append(" / "); + getDenominatorFormat().format(BigFraction.getDenominator(), toAppendTo, pos); + + return toAppendTo; + } + + /** + * Formats an object and appends the result to a StringBuffer. <code>obj</code> must be either a + * {@link BigFraction} object or a {@link BigInteger} object or a {@link Number} object. Any + * other type of object will result in an {@link IllegalArgumentException} being thrown. + * + * @param obj the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment + * field + * @return the value passed in as toAppendTo. + * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, + * java.text.FieldPosition) + * @throws MathIllegalArgumentException if <code>obj</code> is not a valid type. + */ + @Override + public StringBuffer format( + final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) { + + final StringBuffer ret; + if (obj instanceof BigFraction) { + ret = format((BigFraction) obj, toAppendTo, pos); + } else if (obj instanceof BigInteger) { + ret = format(new BigFraction((BigInteger) obj), toAppendTo, pos); + } else if (obj instanceof Number) { + ret = format(new BigFraction(((Number) obj).doubleValue()), toAppendTo, pos); + } else { + throw new MathIllegalArgumentException( + LocalizedFormats.CANNOT_FORMAT_OBJECT_TO_FRACTION); + } + + return ret; + } + + /** + * Parses a string to produce a {@link BigFraction} object. + * + * @param source the string to parse + * @return the parsed {@link BigFraction} object. + * @exception MathParseException if the beginning of the specified string cannot be parsed. + */ + @Override + public BigFraction parse(final String source) throws MathParseException { + final ParsePosition parsePosition = new ParsePosition(0); + final BigFraction result = parse(source, parsePosition); + if (parsePosition.getIndex() == 0) { + throw new MathParseException(source, parsePosition.getErrorIndex(), BigFraction.class); + } + return result; + } + + /** + * Parses a string to produce a {@link BigFraction} object. This method expects the string to be + * formatted as an improper BigFraction. + * + * @param source the string to parse + * @param pos input/output parsing parameter. + * @return the parsed {@link BigFraction} object. + */ + @Override + public BigFraction parse(final String source, final ParsePosition pos) { + final int initialIndex = pos.getIndex(); + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse numerator + final BigInteger num = parseNextBigInteger(source, pos); + if (num == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + // parse '/' + final int startIndex = pos.getIndex(); + final char c = parseNextCharacter(source, pos); + switch (c) { + case 0: + // no '/' + // return num as a BigFraction + return new BigFraction(num); + case '/': + // found '/', continue parsing denominator + break; + default: + // invalid '/' + // set index back to initial, error index should be the last + // character examined. + pos.setIndex(initialIndex); + pos.setErrorIndex(startIndex); + return null; + } + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse denominator + final BigInteger den = parseNextBigInteger(source, pos); + if (den == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + return new BigFraction(num, den); + } + + /** + * Parses a string to produce a <code>BigInteger</code>. + * + * @param source the string to parse + * @param pos input/output parsing parameter. + * @return a parsed <code>BigInteger</code> or null if string does not contain a BigInteger at + * the specified position + */ + protected BigInteger parseNextBigInteger(final String source, final ParsePosition pos) { + + final int start = pos.getIndex(); + int end = (source.charAt(start) == '-') ? (start + 1) : start; + while ((end < source.length()) && Character.isDigit(source.charAt(end))) { + ++end; + } + + try { + BigInteger n = new BigInteger(source.substring(start, end)); + pos.setIndex(end); + return n; + } catch (NumberFormatException nfe) { + pos.setErrorIndex(start); + return null; + } + } +} 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(); + } +} diff --git a/src/main/java/org/apache/commons/math3/fraction/FractionConversionException.java b/src/main/java/org/apache/commons/math3/fraction/FractionConversionException.java new file mode 100644 index 0000000..cc34cae --- /dev/null +++ b/src/main/java/org/apache/commons/math3/fraction/FractionConversionException.java @@ -0,0 +1,56 @@ +/* + * 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.exception.ConvergenceException; +import org.apache.commons.math3.exception.util.LocalizedFormats; + +/** + * Error thrown when a double value cannot be converted to a fraction in the allowed number of + * iterations. + * + * @since 1.2 + */ +public class FractionConversionException extends ConvergenceException { + + /** Serializable version identifier. */ + private static final long serialVersionUID = -4661812640132576263L; + + /** + * 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(LocalizedFormats.FAILED_FRACTION_CONVERSION, value, maxIterations); + } + + /** + * Constructs an exception with specified formatted detail message. Message formatting is + * delegated to {@link java.text.MessageFormat}. + * + * @param value double value to convert + * @param p current numerator + * @param q current denominator + */ + public FractionConversionException(double value, long p, long q) { + super(LocalizedFormats.FRACTION_CONVERSION_OVERFLOW, value, p, q); + } +} diff --git a/src/main/java/org/apache/commons/math3/fraction/FractionField.java b/src/main/java/org/apache/commons/math3/fraction/FractionField.java new file mode 100644 index 0000000..bc00716 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/fraction/FractionField.java @@ -0,0 +1,87 @@ +/* + * 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.Field; +import org.apache.commons.math3.FieldElement; + +import java.io.Serializable; + +/** + * Representation of the fractional numbers field. + * + * <p>This class is a singleton. + * + * @see Fraction + * @since 2.0 + */ +public class FractionField implements Field<Fraction>, Serializable { + + /** Serializable version identifier */ + private static final long serialVersionUID = -1257768487499119313L; + + /** Private constructor for the singleton. */ + private FractionField() {} + + /** + * Get the unique instance. + * + * @return the unique instance + */ + public static FractionField getInstance() { + return LazyHolder.INSTANCE; + } + + /** {@inheritDoc} */ + public Fraction getOne() { + return Fraction.ONE; + } + + /** {@inheritDoc} */ + public Fraction getZero() { + return Fraction.ZERO; + } + + /** {@inheritDoc} */ + public Class<? extends FieldElement<Fraction>> getRuntimeClass() { + return Fraction.class; + } + + // CHECKSTYLE: stop HideUtilityClassConstructor + /** + * Holder for the instance. + * + * <p>We use here the Initialization On Demand Holder Idiom. + */ + private static class LazyHolder { + /** Cached field instance. */ + private static final FractionField INSTANCE = new FractionField(); + } + + // CHECKSTYLE: resume HideUtilityClassConstructor + + /** + * Handle deserialization of the singleton. + * + * @return the singleton instance + */ + private Object readResolve() { + // return the singleton instance + return LazyHolder.INSTANCE; + } +} diff --git a/src/main/java/org/apache/commons/math3/fraction/FractionFormat.java b/src/main/java/org/apache/commons/math3/fraction/FractionFormat.java new file mode 100644 index 0000000..181de7e --- /dev/null +++ b/src/main/java/org/apache/commons/math3/fraction/FractionFormat.java @@ -0,0 +1,270 @@ +/* + * 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.exception.MathIllegalArgumentException; +import org.apache.commons.math3.exception.MathParseException; +import org.apache.commons.math3.exception.util.LocalizedFormats; + +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Locale; + +/** + * Formats a Fraction number in proper format or improper format. The number format for each of the + * whole number, numerator and, denominator can be configured. + * + * @since 1.1 + */ +public class FractionFormat extends AbstractFormat { + + /** Serializable version identifier */ + private static final long serialVersionUID = 3008655719530972611L; + + /** + * Create an improper formatting instance with the default number format for the numerator and + * denominator. + */ + public FractionFormat() {} + + /** + * Create an improper formatting instance with a custom number format for both the numerator and + * denominator. + * + * @param format the custom format for both the numerator and denominator. + */ + public FractionFormat(final NumberFormat format) { + super(format); + } + + /** + * Create an improper formatting instance with a custom number format for the numerator and a + * custom number format for the denominator. + * + * @param numeratorFormat the custom format for the numerator. + * @param denominatorFormat the custom format for the denominator. + */ + public FractionFormat( + final NumberFormat numeratorFormat, final NumberFormat denominatorFormat) { + super(numeratorFormat, denominatorFormat); + } + + /** + * Get the set of locales for which complex formats are available. This is the same set as the + * {@link NumberFormat} set. + * + * @return available complex format locales. + */ + public static Locale[] getAvailableLocales() { + return NumberFormat.getAvailableLocales(); + } + + /** + * This static method calls formatFraction() on a default instance of FractionFormat. + * + * @param f Fraction object to format + * @return a formatted fraction in proper form. + */ + public static String formatFraction(Fraction f) { + return getImproperInstance().format(f); + } + + /** + * Returns the default complex format for the current locale. + * + * @return the default complex format. + */ + public static FractionFormat getImproperInstance() { + return getImproperInstance(Locale.getDefault()); + } + + /** + * Returns the default complex format for the given locale. + * + * @param locale the specific locale used by the format. + * @return the complex format specific to the given locale. + */ + public static FractionFormat getImproperInstance(final Locale locale) { + return new FractionFormat(getDefaultNumberFormat(locale)); + } + + /** + * Returns the default complex format for the current locale. + * + * @return the default complex format. + */ + public static FractionFormat getProperInstance() { + return getProperInstance(Locale.getDefault()); + } + + /** + * Returns the default complex format for the given locale. + * + * @param locale the specific locale used by the format. + * @return the complex format specific to the given locale. + */ + public static FractionFormat getProperInstance(final Locale locale) { + return new ProperFractionFormat(getDefaultNumberFormat(locale)); + } + + /** + * Create a default number format. The default number format is based on {@link + * NumberFormat#getNumberInstance(java.util.Locale)} with the only customizing is the maximum + * number of fraction digits, which is set to 0. + * + * @return the default number format. + */ + protected static NumberFormat getDefaultNumberFormat() { + return getDefaultNumberFormat(Locale.getDefault()); + } + + /** + * Formats a {@link Fraction} object to produce a string. The fraction is output in improper + * format. + * + * @param fraction the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment + * field + * @return the value passed in as toAppendTo. + */ + public StringBuffer format( + final Fraction fraction, final StringBuffer toAppendTo, final FieldPosition pos) { + + pos.setBeginIndex(0); + pos.setEndIndex(0); + + getNumeratorFormat().format(fraction.getNumerator(), toAppendTo, pos); + toAppendTo.append(" / "); + getDenominatorFormat().format(fraction.getDenominator(), toAppendTo, pos); + + return toAppendTo; + } + + /** + * Formats an object and appends the result to a StringBuffer. <code>obj</code> must be either a + * {@link Fraction} object or a {@link Number} object. Any other type of object will result in + * an {@link IllegalArgumentException} being thrown. + * + * @param obj the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment + * field + * @return the value passed in as toAppendTo. + * @see java.text.Format#format(java.lang.Object, java.lang.StringBuffer, + * java.text.FieldPosition) + * @throws FractionConversionException if the number cannot be converted to a fraction + * @throws MathIllegalArgumentException if <code>obj</code> is not a valid type. + */ + @Override + public StringBuffer format( + final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) + throws FractionConversionException, MathIllegalArgumentException { + StringBuffer ret = null; + + if (obj instanceof Fraction) { + ret = format((Fraction) obj, toAppendTo, pos); + } else if (obj instanceof Number) { + ret = format(new Fraction(((Number) obj).doubleValue()), toAppendTo, pos); + } else { + throw new MathIllegalArgumentException( + LocalizedFormats.CANNOT_FORMAT_OBJECT_TO_FRACTION); + } + + return ret; + } + + /** + * Parses a string to produce a {@link Fraction} object. + * + * @param source the string to parse + * @return the parsed {@link Fraction} object. + * @exception MathParseException if the beginning of the specified string cannot be parsed. + */ + @Override + public Fraction parse(final String source) throws MathParseException { + final ParsePosition parsePosition = new ParsePosition(0); + final Fraction result = parse(source, parsePosition); + if (parsePosition.getIndex() == 0) { + throw new MathParseException(source, parsePosition.getErrorIndex(), Fraction.class); + } + return result; + } + + /** + * Parses a string to produce a {@link Fraction} object. This method expects the string to be + * formatted as an improper fraction. + * + * @param source the string to parse + * @param pos input/output parsing parameter. + * @return the parsed {@link Fraction} object. + */ + @Override + public Fraction parse(final String source, final ParsePosition pos) { + final int initialIndex = pos.getIndex(); + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse numerator + final Number num = getNumeratorFormat().parse(source, pos); + if (num == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + // parse '/' + final int startIndex = pos.getIndex(); + final char c = parseNextCharacter(source, pos); + switch (c) { + case 0: + // no '/' + // return num as a fraction + return new Fraction(num.intValue(), 1); + case '/': + // found '/', continue parsing denominator + break; + default: + // invalid '/' + // set index back to initial, error index should be the last + // character examined. + pos.setIndex(initialIndex); + pos.setErrorIndex(startIndex); + return null; + } + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse denominator + final Number den = getDenominatorFormat().parse(source, pos); + if (den == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + return new Fraction(num.intValue(), den.intValue()); + } +} diff --git a/src/main/java/org/apache/commons/math3/fraction/ProperBigFractionFormat.java b/src/main/java/org/apache/commons/math3/fraction/ProperBigFractionFormat.java new file mode 100644 index 0000000..6e034b5 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/fraction/ProperBigFractionFormat.java @@ -0,0 +1,239 @@ +/* + * 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.exception.NullArgumentException; +import org.apache.commons.math3.exception.util.LocalizedFormats; + +import java.math.BigInteger; +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; + +/** + * Formats a BigFraction number in proper format. The number format for each of the whole number, + * numerator and, denominator can be configured. + * + * <p>Minus signs are only allowed in the whole number part - i.e., "-3 1/2" is legitimate and + * denotes -7/2, but "-3 -1/2" is invalid and will result in a <code>ParseException</code>. + * + * @since 1.1 + */ +public class ProperBigFractionFormat extends BigFractionFormat { + + /** Serializable version identifier */ + private static final long serialVersionUID = -6337346779577272307L; + + /** The format used for the whole number. */ + private NumberFormat wholeFormat; + + /** + * Create a proper formatting instance with the default number format for the whole, numerator, + * and denominator. + */ + public ProperBigFractionFormat() { + this(getDefaultNumberFormat()); + } + + /** + * Create a proper formatting instance with a custom number format for the whole, numerator, and + * denominator. + * + * @param format the custom format for the whole, numerator, and denominator. + */ + public ProperBigFractionFormat(final NumberFormat format) { + this(format, (NumberFormat) format.clone(), (NumberFormat) format.clone()); + } + + /** + * Create a proper formatting instance with a custom number format for each of the whole, + * numerator, and denominator. + * + * @param wholeFormat the custom format for the whole. + * @param numeratorFormat the custom format for the numerator. + * @param denominatorFormat the custom format for the denominator. + */ + public ProperBigFractionFormat( + final NumberFormat wholeFormat, + final NumberFormat numeratorFormat, + final NumberFormat denominatorFormat) { + super(numeratorFormat, denominatorFormat); + setWholeFormat(wholeFormat); + } + + /** + * Formats a {@link BigFraction} object to produce a string. The BigFraction is output in proper + * format. + * + * @param fraction the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment + * field + * @return the value passed in as toAppendTo. + */ + @Override + public StringBuffer format( + final BigFraction fraction, final StringBuffer toAppendTo, final FieldPosition pos) { + + pos.setBeginIndex(0); + pos.setEndIndex(0); + + BigInteger num = fraction.getNumerator(); + BigInteger den = fraction.getDenominator(); + BigInteger whole = num.divide(den); + num = num.remainder(den); + + if (!BigInteger.ZERO.equals(whole)) { + getWholeFormat().format(whole, toAppendTo, pos); + toAppendTo.append(' '); + if (num.compareTo(BigInteger.ZERO) < 0) { + num = num.negate(); + } + } + getNumeratorFormat().format(num, toAppendTo, pos); + toAppendTo.append(" / "); + getDenominatorFormat().format(den, toAppendTo, pos); + + return toAppendTo; + } + + /** + * Access the whole format. + * + * @return the whole format. + */ + public NumberFormat getWholeFormat() { + return wholeFormat; + } + + /** + * Parses a string to produce a {@link BigFraction} object. This method expects the string to be + * formatted as a proper BigFraction. + * + * <p>Minus signs are only allowed in the whole number part - i.e., "-3 1/2" is legitimate and + * denotes -7/2, but "-3 -1/2" is invalid and will result in a <code>ParseException</code>. + * + * @param source the string to parse + * @param pos input/ouput parsing parameter. + * @return the parsed {@link BigFraction} object. + */ + @Override + public BigFraction parse(final String source, final ParsePosition pos) { + // try to parse improper BigFraction + BigFraction ret = super.parse(source, pos); + if (ret != null) { + return ret; + } + + final int initialIndex = pos.getIndex(); + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse whole + BigInteger whole = parseNextBigInteger(source, pos); + if (whole == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse numerator + BigInteger num = parseNextBigInteger(source, pos); + if (num == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + if (num.compareTo(BigInteger.ZERO) < 0) { + // minus signs should be leading, invalid expression + pos.setIndex(initialIndex); + return null; + } + + // parse '/' + final int startIndex = pos.getIndex(); + final char c = parseNextCharacter(source, pos); + switch (c) { + case 0: + // no '/' + // return num as a BigFraction + return new BigFraction(num); + case '/': + // found '/', continue parsing denominator + break; + default: + // invalid '/' + // set index back to initial, error index should be the last + // character examined. + pos.setIndex(initialIndex); + pos.setErrorIndex(startIndex); + return null; + } + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse denominator + final BigInteger den = parseNextBigInteger(source, pos); + if (den == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + if (den.compareTo(BigInteger.ZERO) < 0) { + // minus signs must be leading, invalid + pos.setIndex(initialIndex); + return null; + } + + boolean wholeIsNeg = whole.compareTo(BigInteger.ZERO) < 0; + if (wholeIsNeg) { + whole = whole.negate(); + } + num = whole.multiply(den).add(num); + if (wholeIsNeg) { + num = num.negate(); + } + + return new BigFraction(num, den); + } + + /** + * Modify the whole format. + * + * @param format The new whole format value. + * @throws NullArgumentException if {@code format} is {@code null}. + */ + public void setWholeFormat(final NumberFormat format) { + if (format == null) { + throw new NullArgumentException(LocalizedFormats.WHOLE_FORMAT); + } + this.wholeFormat = format; + } +} diff --git a/src/main/java/org/apache/commons/math3/fraction/ProperFractionFormat.java b/src/main/java/org/apache/commons/math3/fraction/ProperFractionFormat.java new file mode 100644 index 0000000..3f74f16 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/fraction/ProperFractionFormat.java @@ -0,0 +1,231 @@ +/* + * 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.exception.NullArgumentException; +import org.apache.commons.math3.exception.util.LocalizedFormats; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.MathUtils; + +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; + +/** + * Formats a Fraction number in proper format. The number format for each of the whole number, + * numerator and, denominator can be configured. + * + * <p>Minus signs are only allowed in the whole number part - i.e., "-3 1/2" is legitimate and + * denotes -7/2, but "-3 -1/2" is invalid and will result in a <code>ParseException</code>. + * + * @since 1.1 + */ +public class ProperFractionFormat extends FractionFormat { + + /** Serializable version identifier */ + private static final long serialVersionUID = 760934726031766749L; + + /** The format used for the whole number. */ + private NumberFormat wholeFormat; + + /** + * Create a proper formatting instance with the default number format for the whole, numerator, + * and denominator. + */ + public ProperFractionFormat() { + this(getDefaultNumberFormat()); + } + + /** + * Create a proper formatting instance with a custom number format for the whole, numerator, and + * denominator. + * + * @param format the custom format for the whole, numerator, and denominator. + */ + public ProperFractionFormat(NumberFormat format) { + this(format, (NumberFormat) format.clone(), (NumberFormat) format.clone()); + } + + /** + * Create a proper formatting instance with a custom number format for each of the whole, + * numerator, and denominator. + * + * @param wholeFormat the custom format for the whole. + * @param numeratorFormat the custom format for the numerator. + * @param denominatorFormat the custom format for the denominator. + */ + public ProperFractionFormat( + NumberFormat wholeFormat, + NumberFormat numeratorFormat, + NumberFormat denominatorFormat) { + super(numeratorFormat, denominatorFormat); + setWholeFormat(wholeFormat); + } + + /** + * Formats a {@link Fraction} object to produce a string. The fraction is output in proper + * format. + * + * @param fraction the object to format. + * @param toAppendTo where the text is to be appended + * @param pos On input: an alignment field, if desired. On output: the offsets of the alignment + * field + * @return the value passed in as toAppendTo. + */ + @Override + public StringBuffer format(Fraction fraction, StringBuffer toAppendTo, FieldPosition pos) { + + pos.setBeginIndex(0); + pos.setEndIndex(0); + + int num = fraction.getNumerator(); + int den = fraction.getDenominator(); + int whole = num / den; + num %= den; + + if (whole != 0) { + getWholeFormat().format(whole, toAppendTo, pos); + toAppendTo.append(' '); + num = FastMath.abs(num); + } + getNumeratorFormat().format(num, toAppendTo, pos); + toAppendTo.append(" / "); + getDenominatorFormat().format(den, toAppendTo, pos); + + return toAppendTo; + } + + /** + * Access the whole format. + * + * @return the whole format. + */ + public NumberFormat getWholeFormat() { + return wholeFormat; + } + + /** + * Parses a string to produce a {@link Fraction} object. This method expects the string to be + * formatted as a proper fraction. + * + * <p>Minus signs are only allowed in the whole number part - i.e., "-3 1/2" is legitimate and + * denotes -7/2, but "-3 -1/2" is invalid and will result in a <code>ParseException</code>. + * + * @param source the string to parse + * @param pos input/ouput parsing parameter. + * @return the parsed {@link Fraction} object. + */ + @Override + public Fraction parse(String source, ParsePosition pos) { + // try to parse improper fraction + Fraction ret = super.parse(source, pos); + if (ret != null) { + return ret; + } + + int initialIndex = pos.getIndex(); + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse whole + Number whole = getWholeFormat().parse(source, pos); + if (whole == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse numerator + Number num = getNumeratorFormat().parse(source, pos); + if (num == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + if (num.intValue() < 0) { + // minus signs should be leading, invalid expression + pos.setIndex(initialIndex); + return null; + } + + // parse '/' + int startIndex = pos.getIndex(); + char c = parseNextCharacter(source, pos); + switch (c) { + case 0: + // no '/' + // return num as a fraction + return new Fraction(num.intValue(), 1); + case '/': + // found '/', continue parsing denominator + break; + default: + // invalid '/' + // set index back to initial, error index should be the last + // character examined. + pos.setIndex(initialIndex); + pos.setErrorIndex(startIndex); + return null; + } + + // parse whitespace + parseAndIgnoreWhitespace(source, pos); + + // parse denominator + Number den = getDenominatorFormat().parse(source, pos); + if (den == null) { + // invalid integer number + // set index back to initial, error index should already be set + // character examined. + pos.setIndex(initialIndex); + return null; + } + + if (den.intValue() < 0) { + // minus signs must be leading, invalid + pos.setIndex(initialIndex); + return null; + } + + int w = whole.intValue(); + int n = num.intValue(); + int d = den.intValue(); + return new Fraction(((FastMath.abs(w) * d) + n) * MathUtils.copySign(1, w), d); + } + + /** + * Modify the whole format. + * + * @param format The new whole format value. + * @throws NullArgumentException if {@code format} is {@code null}. + */ + public void setWholeFormat(NumberFormat format) { + if (format == null) { + throw new NullArgumentException(LocalizedFormats.WHOLE_FORMAT); + } + this.wholeFormat = format; + } +} diff --git a/src/main/java/org/apache/commons/math3/fraction/package-info.java b/src/main/java/org/apache/commons/math3/fraction/package-info.java new file mode 100644 index 0000000..9d05869 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/fraction/package-info.java @@ -0,0 +1,18 @@ +/* + * 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. + */ +/** Fraction number type and fraction number formatting. */ +package org.apache.commons.math3.fraction; |