All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.numbers.fraction.Fraction Maven / Gradle / Ivy

/*
 * 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.numbers.fraction;

import java.io.Serializable;
import org.apache.commons.numbers.core.ArithmeticUtils;
import org.apache.commons.numbers.core.NativeOperators;

/**
 * Representation of a rational number.
 */
public final class Fraction
    extends Number
    implements Comparable,
               NativeOperators,
               Serializable {
    /** 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);
    /** Serializable version identifier. */
    private static final long serialVersionUID = 20190701L;
    /** The default epsilon used for convergence. */
    private static final double DEFAULT_EPSILON = 1e-5;
    /** The denominator of this fraction reduced to lowest terms. */
    private final int denominator;
    /** The numerator of this fraction reduced to lowest terms. */
    private final int numerator;

    /**
     * Create a fraction given the double value and either the maximum error
     * allowed or the maximum number of denominator digits.
     * 

* * 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). *

* * 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: * 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 ArithmeticException if the continued fraction failed * to converge. */ private Fraction(double value, double epsilon, int maxDenominator, int maxIterations) { final long overflow = Integer.MAX_VALUE; double r0 = value; long a0 = (long)Math.floor(r0); if (Math.abs(a0) > overflow) { throw new FractionException(FractionException.ERROR_CONVERSION, value, a0, 1L); } // check for (almost) integer arguments, which should not go to iterations. if (Math.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; final double r1 = 1.0 / (r0 - a0); final long a1 = (long)Math.floor(r1); p2 = (a1 * p1) + p0; q2 = (a1 * q1) + q0; if (Math.abs(p2) > overflow || Math.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 && Math.abs(q1) < maxDenominator) { break; } throw new FractionException(FractionException.ERROR_CONVERSION, value, p2, q2); } final double convergent = (double)p2 / (double)q2; if (n < maxIterations && Math.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 FractionException(FractionException.ERROR_CONVERSION, value, maxIterations); } if (q2 < maxDenominator) { this.numerator = (int) p2; this.denominator = (int) q2; } else { this.numerator = (int) p1; this.denominator = (int) q1; } } /** * Constructs an instance. * * @param num Numerator. * @param den Nenominator. * @throws ArithmeticException if the denominator is {@code zero} * or if integer overflow occurs. */ private Fraction(int num, int den) { if (den == 0) { throw new ArithmeticException("division by zero"); } if (num == den) { numerator = 1; denominator = 1; } else { // If num and den are both 2^-31, or if one is 0 and the other is 2^-31, // the calculation of the gcd below will fail. Ensure that this does not // happen by dividing both by 2 in case both are even. if (((num | den) & 1) == 0) { num >>= 1; den >>= 1; } // Reduce numerator and denominator by greatest common divisor. final int d = ArithmeticUtils.gcd(num, den); if (d > 1) { num /= d; den /= d; } numerator = num; denominator = den; } } /** * Creates an instance. * * @param value Value to convert to a fraction. * @throws ArithmeticException if the continued fraction failed to * converge. * @return a new instance. */ public static Fraction from(double value) { return from(value, DEFAULT_EPSILON, 100); } /** * Create a fraction given the double value and maximum error allowed. * *

* References: *

* * @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 ArithmeticException if the continued fraction failed to * converge. * @return a new instance. */ public static Fraction from(double value, double epsilon, int maxIterations) { return new Fraction(value, epsilon, Integer.MAX_VALUE, maxIterations); } /** * Creates an instance. * *

* References: *

* * @param value the double value to convert to a fraction. * @param maxDenominator The maximum allowed value for denominator * @throws ArithmeticException if the continued fraction failed to * converge. * @return a new instance. */ public static Fraction from(double value, int maxDenominator) { return new Fraction(value, 0, maxDenominator, 100); } /** * Creates an instance. * The fraction is {@code num / 1}. * * @param num Numerator. * @return a new instance. */ public static Fraction of(int num) { return of(num, 1); } /** * Return a fraction given the numerator and denominator. * The fraction is reduced to lowest terms. * * @param num Numerator. * @param den Denominator. * @throws ArithmeticException if the denominator is {@code zero} * or if integer overflow occurs. * @return a new instance. */ public static Fraction of(int num, int den) { return new Fraction(num, den); } /** * Returns the absolute value of this fraction. * * @return the absolute value. */ public Fraction abs() { return signum() >= 0 ? this : negate(); } /** * Compares this object to another based on size. * * @param other 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. */ @Override public int compareTo(Fraction other) { return Long.compare(((long) numerator) * other.denominator, ((long) denominator) * other.numerator); } /** * Retrieves the {@code double} value closest to this fraction. * This calculates the fraction as 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 with. * @return {@code true} if the two fractions are equal, {@code false} * otherwise. */ @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. final Fraction rhs = (Fraction) other; if (signum() == rhs.signum()) { return Math.abs(numerator) == Math.abs(rhs.numerator) && Math.abs(denominator) == Math.abs(rhs.denominator); } else { return false; } } return false; } /** * Retrieves the {@code float} value closest to this fraction. * This calculates the fraction as numerator divided by denominator. * * @return the fraction as {@code float}. */ @Override public float floatValue() { return (float) doubleValue(); } /** * @return the denominator. */ public int getDenominator() { return denominator; } /** * @return the numerator. */ public int getNumerator() { return numerator; } /** {@inheritDoc} */ @Override public int hashCode() { return 37 * (37 * 17 + numerator) + denominator; } /** * Retrieves the whole number part of the fraction. * * @return the largest {@code int} value that is not larger than * this fraction. */ @Override public int intValue() { return (int) doubleValue(); } /** * Retrieves the whole number part of the fraction. * * @return the largest {@code long} value that is not larger than * this fraction. */ @Override public long longValue() { return (long) doubleValue(); } /** * Retrieves the sign of this fraction. * * @return -1 if the value is strictly negative, 1 if it is strictly * positive, 0 if it is 0. */ public int signum() { if ((numerator > 0 && denominator > 0) || (numerator < 0 && denominator < 0)) { return 1; } else if (numerator == 0) { return 0; } else { return -1; } } /** * Computes the additive inverse of this fraction. * * @return the opposite. */ @Override public Fraction negate() { return numerator == Integer.MIN_VALUE ? new Fraction(numerator, -denominator) : new Fraction(-numerator, denominator); } /** * Computes the multiplicative inverse of this fraction. * * @return the reciprocal. */ @Override 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 Fraction to add. * @return a new instance. * @throws ArithmeticException if the resulting numerator or denominator * cannot be represented in an {@code int}. */ @Override public Fraction add(Fraction fraction) { return addSub(fraction, true /* add */); } /** * Adds an integer to the fraction. * * @param i Value to add. * @return {@code 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 Fraction to subtract. * @return a new instance. * @throws ArithmeticException if the resulting numerator or denominator * cannot be represented in an {@code int}. */ @Override public Fraction subtract(Fraction fraction) { return addSub(fraction, false /* subtract */); } /** * Subtracts an integer from this fraction. * * @param i Value to subtract. * @return {@code this - i}. */ public Fraction subtract(final int i) { return new Fraction(numerator - i * denominator, denominator); } /** * Implements add and subtract using algorithm described in Knuth 4.5.1. * * @param fraction Fraction to add or subtract. * @param isAdd Whether the operation is "add" or "subtract". * @return a new instance. * @throws ArithmeticException if the resulting numerator or denominator * cannot be represented in an {@code int}. */ private Fraction addSub(Fraction fraction, boolean isAdd) { // Zero is identity for addition. if (numerator == 0) { return isAdd ? fraction : fraction.negate(); } if (fraction.numerator == 0) { return this; } /* * Let the two fractions be u/u' and v/v', and d1 = gcd(u', v'). * First, compute t, defined as: * * t = u(v'/d1) +/- v(u'/d1) */ final int d1 = ArithmeticUtils.gcd(denominator, fraction.denominator); final long uvp = (long) numerator * (long) (fraction.denominator / d1); final long upv = (long) fraction.numerator * (long) (denominator / d1); /* * The largest possible absolute value of a product of two ints is 2^62, * which can only happen as a result of -2^31 * -2^31 = 2^62, so a * product of -2^62 is not possible. It follows that (uvp - upv) cannot * overflow, and (uvp + upv) could only overflow if uvp = upv = 2^62. * But for this to happen, the terms u, v, v'/d1 and u'/d1 would all * have to be -2^31, which is not possible because v'/d1 and u'/d1 * are necessarily coprime. */ final long t = isAdd ? uvp + upv : uvp - upv; /* * Because u is coprime to u' and v is coprime to v', t is necessarily * coprime to both v'/d1 and u'/d1. However, it might have a common * factor with d1. */ final long d2 = ArithmeticUtils.gcd(t, d1); // result is (t/d2) / (u'/d1)(v'/d2) return of(Math.toIntExact(t / d2), Math.multiplyExact(denominator / d1, fraction.denominator / (int) d2)); } /** * Multiplies the value of this fraction by another, returning the * result in reduced form. * * @param fraction Fraction to multiply by. * @return a new instance. * @throws ArithmeticException if the resulting numerator or denominator * cannot be represented in an {@code int}. */ @Override public Fraction multiply(Fraction fraction) { if (numerator == 0 || fraction.numerator == 0) { return ZERO; } // knuth 4.5.1 // Make sure we don't overflow unless the result *must* overflow. final int d1 = ArithmeticUtils.gcd(numerator, fraction.denominator); final int d2 = ArithmeticUtils.gcd(fraction.numerator, denominator); return of(Math.multiplyExact(numerator / d1, fraction.numerator / d2), Math.multiplyExact(denominator / d2, fraction.denominator / d1)); } /** * Multiplies the fraction by an integer. * * @param i Value to multiply by. * @return {@code this * i}. */ @Override public Fraction multiply(final int i) { return multiply(of(i)); } /** * Divides the value of this fraction by another. * * @param fraction Fraction to divide by. * @return a new instance. * @throws ArithmeticException if the fraction to divide by is zero * or if the resulting numerator or denominator cannot be represented * by an {@code int}. */ @Override public Fraction divide(Fraction fraction) { if (fraction.numerator == 0) { throw new FractionException("the fraction to divide by must not be zero: {0}/{1}", fraction.numerator, fraction.denominator); } return multiply(fraction.reciprocal()); } /** * Divides the fraction by an integer. * * @param i Value to divide by. * @return {@code this * i}. */ public Fraction divide(final int i) { return divide(of(i)); } /** * @param n Power. * @return thisn. */ @Override public Fraction pow(final int n) { if (n == 0) { return ONE; } if (numerator == 0) { return this; } return n < 0 ? new Fraction(ArithmeticUtils.pow(denominator, -n), ArithmeticUtils.pow(numerator, -n)) : new Fraction(ArithmeticUtils.pow(numerator, n), ArithmeticUtils.pow(denominator, n)); } /** {@inheritDoc} */ @Override public String toString() { final String str; if (denominator == 1) { str = Integer.toString(numerator); } else if (numerator == 0) { str = "0"; } else { str = numerator + " / " + denominator; } return str; } /** {@inheritDoc} */ @Override public Fraction zero() { return ZERO; } /** {@inheritDoc} */ @Override public Fraction one() { return ONE; } /** * Parses a string that would be produced by {@link #toString()} * and instantiates the corresponding object. * * @param s String representation. * @return an instance. * @throws NumberFormatException if the string does not conform to the * specification. */ public static Fraction parse(String s) { final int slashLoc = s.indexOf('/'); // if no slash, parse as single number if (slashLoc == -1) { return Fraction.of(Integer.parseInt(s.trim())); } else { final int num = Integer.parseInt(s.substring(0, slashLoc).trim()); final int denom = Integer.parseInt(s.substring(slashLoc + 1).trim()); return of(num, denom); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy