/*
* Universal Media Server, for streaming any media to DLNA
* compatible renderers based on the http://www.ps3mediaserver.org.
* Copyright (C) 2012 UMS developers.
*
* This program is a free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License only.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package net.pms.util;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This is an immutable class for holding a rational without loss of precision.
* As a consequence, a new instance is generated for every mathematical
* operation. The reduced rationale and most string values are generated during
* construction and cached for later retrieval.
* <p>
* This class has methods for doing basic operations mathematical operations,
* and constructors for various number formats. This class implements
* {@link Number} for easy conversion to other number formats, and
* {@link Comparable} so two values can be compared and the values can be
* sorted. In addition to {@link Comparable#compareTo}, there are
* {@link #compareTo} methods that accepts most primitive number types and any
* class implementing {@link Number}.
* <p>
* All the {@link #compareTo} can be used as a "replacement" for the six boolean comparison operators ({@literal <}, ==, {@literal >},
* {@literal >=}, !=, {@literal <=}) since these are not supported for "custom" number types.
* The suggested idiom for performing
* these comparisons is: {@code (x.compareTo(y)} <<i>op</i>>
* {@code 0)}, where <<i>op</i>> is one of the six comparison
* operators.
* <p>
* Static methods for finding the greatest common divisor and the least common
* multiple for two integers are also provided with
* {@link #getGreatestCommonDivisor} and {@link #getLeastCommonMultiple}.
*
* This class is "inspired" by the following:
* <ul>
* <li>http://introcs.cs.princeton.edu/java/32class/Rational.java.html</li>
* <li>com.drew.lang.Rational</li>
* <li>org.apache.commons.lang.math.Fraction</li>
* <li>com.twelvemonkeys.imageio.metadata.exif.Rational</li>
* <li>java.math.BigInteger</li>
* <li>java.math.BigDecimal</li>
* <li>java.math.MutableBigInteger</li>
* </ul>
*
* @author Nadahar
*/
public class Rational extends Number implements Comparable<Rational> {
/** The static instance representing the value 0 */
public static final Rational ZERO = new Rational();
/** The static instance representing the value 1 */
public static final Rational ONE = new Rational(1L);
/** Internal static string for 0 */
protected static final String STRING_ZERO = "0";
/** Internal static string for 1 */
protected static final String STRING_ONE = "1";
/** Internal regex pattern for validation of strings for parsing */
protected static final Pattern STRING_PATTERN = Pattern.compile("^\\s*(-?\\d+)\\s*(?:/\\s*(-?\\d+))?\\s*$");
/**
* The locale-insensitive {@link DecimalFormat} used for
* {@link #toDecimalString} and {@link #toDebugString}.
*/
protected static final DecimalFormat DECIMALFORMAT = new DecimalFormat(
"#0.####################",
DecimalFormatSymbols.getInstance(Locale.ROOT)
);
private static final long serialVersionUID = 1L;
/** The numerator, which also holds the sign of this {@link Rational}. */
protected final BigInteger numerator;
/** The denominator, which is positive by definition. */
protected final BigInteger denominator;
/**
* The numerator of the reduced {@link Rational}, which also holds the sign.
*/
protected final BigInteger reducedNumerator;
/** The denominator of the reduced {@link Rational}, always positive. */
protected final BigInteger reducedDenominator;
/**
* The greatest common divisor of {@code numerator} and {@code denominator}.
* This is the number by which {@code numerator} and {@code denominator} are
* divided to produce {@code reducedNumerator} and {@code reducedDenominator}
*/
protected final BigInteger greatestCommonDivisor;
/** The cached string value */
protected final String stringValue;
/** The cached reduced string value */
protected final String reducedStringValue;
/** The cached decimal string value */
protected final String decimalStringValue;
/** The cached hashCode */
protected final int hashCode;
/**
* Creates a new instance that represents the value zero. Use {@link #ZERO}
* instead.
*/
protected Rational() {
numerator = BigInteger.ZERO;
denominator = BigInteger.ONE;
greatestCommonDivisor = BigInteger.ONE;
reducedNumerator = numerator;
reducedDenominator = denominator;
stringValue = STRING_ZERO;
reducedStringValue = STRING_ZERO;
decimalStringValue = STRING_ZERO;
hashCode = calculateHashCode();
}
/**
* Creates a new instance that represents the value of {@code value}.
*
* @param value the value.
*/
public Rational(double value) {
this(BigDecimal.valueOf(value));
}
/**
* Creates a new instance that represents the value of {@code value}.
*
* @param value the value.
*/
public Rational(BigDecimal value) {
if (value.signum() == 0) {
this.numerator = BigInteger.ZERO;
denominator = BigInteger.ONE;
} else {
if (value.scale() > 0) {
BigInteger unscaled = value.unscaledValue();
BigInteger tmpDenominator = BigInteger.TEN.pow(value.scale());
BigInteger tmpGreatestCommonDivisor = unscaled.gcd(tmpDenominator);
numerator = unscaled.divide(tmpGreatestCommonDivisor);
denominator = tmpDenominator.divide(tmpGreatestCommonDivisor);
} else {
numerator = value.toBigIntegerExact();
denominator = BigInteger.ONE;
}
}
greatestCommonDivisor = BigInteger.ONE;
reducedNumerator = numerator;
reducedDenominator = denominator;
stringValue = generateRationalString(numerator, denominator);
reducedStringValue = stringValue;
decimalStringValue = generateDecimalString(numerator, denominator);
hashCode = calculateHashCode();
}
/**
* Creates a new instance by parsing {@code value}. The format must be
* either {@code integer numerator/integer denominator} or
* {@code integer numerator} for the parsing to succeed. Signs are
* understood for both numerator and denominator. If {@code value} is blank
* or {@code null}, a {@link Rational} instance representing zero is
* created. If {@code value} can't be parsed, a
* {@link NumberFormatException} is thrown.
*
* @param value the {@link String} value to parse.
* @throws NumberFormatException If {@code value} cannot be parsed.
*/
public Rational(String value) {
if (isBlank(value)) {
numerator = BigInteger.ZERO;
denominator = BigInteger.ONE;
greatestCommonDivisor = BigInteger.ONE;
reducedNumerator = numerator;
reducedDenominator = denominator;
stringValue = STRING_ZERO;
reducedStringValue = STRING_ZERO;
decimalStringValue = STRING_ZERO;
} else {
Matcher matcher = STRING_PATTERN.matcher(value);
if (!matcher.find()) {
throw new NumberFormatException(
"Invalid value \"" + value +
"\". The value must be either \"integer/integer\" or \"integer\""
);
}
if (value.indexOf("/") > 0) {
if (matcher.group(2).startsWith("-")) {
// Keep the signum in the numerator
numerator = new BigInteger(matcher.group(1)).negate();
denominator = new BigInteger(matcher.group(2)).negate();
} else {
numerator = new BigInteger(matcher.group(1));
denominator = new BigInteger(matcher.group(2));
}
greatestCommonDivisor = calculateGreatestCommonDivisor(numerator, denominator);
reducedNumerator = numerator.divide(greatestCommonDivisor);
reducedDenominator = denominator.divide(greatestCommonDivisor);
stringValue = generateRationalString(numerator, denominator);
reducedStringValue = generateRationalString(reducedNumerator, reducedDenominator);
} else {
numerator = new BigInteger(matcher.group(1));
denominator = BigInteger.ONE;
greatestCommonDivisor = BigInteger.ONE;
reducedNumerator = numerator;
reducedDenominator = denominator;
stringValue = generateRationalString(numerator, denominator);
reducedStringValue = stringValue;
}
decimalStringValue = generateDecimalString(reducedNumerator, reducedDenominator);
}
hashCode = calculateHashCode();
}
/**
* Creates a new instance that represents the value of {@code value}.
*
* @param value the value.
*/
public Rational(int value) {
this((long) value);
}
/**
* Creates a new instance that represents the value of {@code value}.
*
* @param value the value.
*/
public Rational(long value) {
numerator = BigInteger.valueOf(value);
denominator = BigInteger.ONE;
greatestCommonDivisor = BigInteger.ONE;
reducedNumerator = numerator;
reducedDenominator = denominator;
stringValue = Long.toString(value);
reducedStringValue = stringValue;
decimalStringValue = stringValue;
hashCode = calculateHashCode();
}
/**
* Creates a new instance that represents the value of {@code value}.
*
* @param value the value.
*/
public Rational(BigInteger value) {
numerator = value;
denominator = BigInteger.ONE;
greatestCommonDivisor = BigInteger.ONE;
reducedNumerator = numerator;
reducedDenominator = denominator;
stringValue = generateRationalString(numerator, denominator);
reducedStringValue = stringValue;
decimalStringValue = stringValue;
hashCode = calculateHashCode();
}
/**
* Creates a new instance with the given {@code numerator} and
* {@code denominator}.
*
* @param numerator the numerator.
* @param denominator the denominator.
*/
public Rational(int numerator, int denominator) {
this((long) numerator, (long) denominator);
}
/**
* Creates a new instance with the given {@code numerator} and
* {@code denominator}.
*
* @param numerator the numerator.
* @param denominator the denominator.
*/
public Rational(long numerator, long denominator) {
if (denominator == 0) {
throw new IllegalArgumentException("denominator can't be zero");
}
if (numerator == 0) {
this.numerator = BigInteger.ZERO;
this.denominator = BigInteger.ONE;
} else if (denominator < 0) {
this.numerator = BigInteger.valueOf(-numerator);
this.denominator = BigInteger.valueOf(-denominator);
} else {
this.numerator = BigInteger.valueOf(numerator);
this.denominator = BigInteger.valueOf(denominator);
}
stringValue = generateRationalString(this.numerator, this.denominator);
long gcd = calculateGreatestCommonDivisor(numerator, denominator);
this.greatestCommonDivisor = BigInteger.valueOf(gcd);
if (gcd == 1) {
this.reducedNumerator = this.numerator;
this.reducedDenominator = this.denominator;
reducedStringValue = stringValue;
} else {
this.reducedNumerator = this.numerator.divide(this.greatestCommonDivisor);
this.reducedDenominator = this.denominator.divide(this.greatestCommonDivisor);
reducedStringValue = generateRationalString(this.reducedNumerator, this.reducedDenominator);
}
decimalStringValue = generateDecimalString(this.reducedNumerator, this.reducedDenominator);
hashCode = calculateHashCode();
}
/**
* Creates a new instance with the given {@code numerator} and
* {@code denominator}.
*
* @param numerator the numerator.
* @param denominator the denominator.
*/
public Rational(BigInteger numerator, BigInteger denominator) {
if (denominator == null || denominator.signum() == 0) {
throw new IllegalArgumentException("denominator can't be zero");
}
if (numerator == null || numerator.signum() == 0) {
this.numerator = BigInteger.ZERO;
this.denominator = BigInteger.ONE;
} else if (denominator.signum() < 0) {
this.numerator = numerator.negate();
this.denominator = denominator.negate();
} else {
this.numerator = numerator;
this.denominator = denominator;
}
stringValue = generateRationalString(this.numerator, this.denominator);
this.greatestCommonDivisor = calculateGreatestCommonDivisor(this.numerator, this.denominator);
if (BigInteger.ONE.equals(this.greatestCommonDivisor)) {
this.reducedNumerator = this.numerator;
this.reducedDenominator = this.denominator;
reducedStringValue = stringValue;
} else {
this.reducedNumerator = this.numerator.divide(this.greatestCommonDivisor);
this.reducedDenominator = this.denominator.divide(this.greatestCommonDivisor);
reducedStringValue = generateRationalString(this.reducedNumerator, this.reducedDenominator);
}
decimalStringValue = generateDecimalString(this.reducedNumerator, this.reducedDenominator);
hashCode = calculateHashCode();
}
// Operations
/**
* Returns a {@link Rational} whose value is {@code (this * value)}.
*
* @param value the value to be multiplied by this {@link Rational}.
* @return The multiplication result.
*/
public Rational multiply(int value) {
return multiply(BigInteger.valueOf(value));
}
/**
* Returns a {@link Rational} whose value is {@code (this * value)}.
*
* @param value the value to be multiplied by this {@link Rational}.
* @return The multiplication result.
*/
public Rational multiply(long value) {
return multiply(BigInteger.valueOf(value));
}
/**
* Returns a {@link Rational} whose value is {@code (this * value)}.
*
* @param value the value to be multiplied by this {@link Rational}.
* @return The multiplication result.
*/
public Rational multiply(BigInteger value) {
if (value == null || value.signum() == 0) {
return ZERO;
}
if (BigInteger.ONE.equals(value.abs())) {
return value.signum() < 0 ? negate() : this;
}
return new Rational(reducedNumerator.multiply(value), reducedDenominator);
}
/**
* Returns a {@link Rational} whose value is {@code (this * value)}.
*
* @param value the value to be multiplied by this {@link Rational}.
* @return The multiplication result.
*/
public Rational multiply(double value) {
return multiply(new Rational(value));
}
/**
* Returns a {@link Rational} whose value is {@code (this * value)}.
*
* @param value the value to be multiplied by this {@link Rational}.
* @return The multiplication result.
*/
public Rational multiply(BigDecimal value) {
return multiply(new Rational(value));
}
/**
* Returns a {@link Rational} whose value is {@code (this * value)}.
*
* @param value the value to be multiplied by this {@link Rational}.
* @return The multiplication result.
*/
public Rational multiply(Rational value) {
if (value == null || value.numerator.signum() == 0) {
return ZERO;
}
if (value.numerator.equals(value.denominator)) {
return value.numerator.signum() < 0 ? this.negate() : this;
}
BigInteger newNumerator = reducedNumerator.multiply(value.reducedNumerator);
BigInteger newDenominator = reducedDenominator.multiply(value.reducedDenominator);
BigInteger gcd = newNumerator.gcd(newDenominator);
return new Rational(newNumerator.divide(gcd), newDenominator.divide(gcd));
}
/**
* Returns a {@link Rational} whose value is {@code (this - value)}.
*
* @param value the value to be subtracted from this {@link Rational}.
* @return The subtraction result.
*/
public Rational subtract(int value) {
return add(-value);
}
/**
* Returns a {@link Rational} whose value is {@code (this - value)}.
*
* @param value the value to be subtracted from this {@link Rational}.
* @return The subtraction result.
*/
public Rational subtract(long value) {
return add(-value);
}
/**
* Returns a {@link Rational} whose value is {@code (this - value)}.
*
* @param value the value to be subtracted from this {@link Rational}.
* @return The subtraction result.
*/
public Rational subtract(BigInteger value) {
return add(value.negate());
}
/**
* Returns a {@link Rational} whose value is {@code (this - value)}.
*
* @param value the value to be subtracted from this {@link Rational}.
* @return The subtraction result.
*/
public Rational subtract(double value) {
return add(-value);
}
/**
* Returns a {@link Rational} whose value is {@code (this - value)}.
*
* @param value the value to be subtracted from this {@link Rational}.
* @return The subtraction result.
*/
public Rational subtract(BigDecimal value) {
return add(value.negate());
}
/**
* Returns a {@link Rational} whose value is {@code (this - value)}.
*
* @param value the value to be subtracted from this {@link Rational}.
* @return The subtraction result.
*/
public Rational subtract(Rational value) {
if (value == null || value.numerator.signum() == 0) {
return this;
}
return add(value.negate());
}
/**
* Returns a {@link Rational} whose value is {@code (this + value)}.
*
* @param value the value to be added to this {@link Rational}.
* @return The addition result.
*/
public Rational add(int value) {
return add((long) value);
}
/**
* Returns a {@link Rational} whose value is {@code (this + value)}.
*
* @param value the value to be added to this {@link Rational}.
* @return The addition result.
*/
public Rational add(long value) {
return add(BigInteger.valueOf(value));
}
/**
* Returns a {@link Rational} whose value is {@code (this + value)}.
*
* @param value the value to be added to this {@link Rational}.
* @return The addition result.
*/
public Rational add(BigInteger value) {
if (value == null || value.signum() == 0) {
return this;
}
if (BigInteger.ONE.equals(denominator)) {
return new Rational(numerator.add(value), denominator);
}
return new Rational(numerator.add(value.multiply(denominator)), denominator);
}
/**
* Returns a {@link Rational} whose value is {@code (this + value)}.
*
* @param value the value to be added to this {@link Rational}.
* @return The addition result.
*/
public Rational add(double value) {
return add(new Rational(value));
}
/**
* Returns a {@link Rational} whose value is {@code (this + value)}.
*
* @param value the value to be added to this {@link Rational}.
* @return The addition result.
*/
public Rational add(BigDecimal value) {
return add(new Rational(value));
}
/**
* Returns a {@link Rational} whose value is {@code (this + value)}.
*
* @param value the value to be added to this {@link Rational}.
* @return The addition result.
*/
public Rational add(Rational value) {
if (value == null || value.numerator.signum() == 0) {
return this;
}
if (this.numerator.signum() == 0) {
return value;
}
BigInteger lcm = calculateLeastCommonMultiple(denominator, value.denominator);
return new Rational(numerator.multiply(lcm.divide(denominator)).add(
value.numerator.multiply(lcm.divide(value.denominator))), lcm);
}
/**
* Returns a {@link Rational} whose value is the reciprocal of this
* {@code (1 / this)}.
*
* @return The reciprocal result.
*/
public Rational reciprocal() {
return numerator.signum() == 0 ? this : new Rational(denominator, numerator);
}
/**
* Returns a {@link Rational} whose value is {@code (this / value)}.
*
* @param value the value by which this {@link Rational} is to be divided.
* @return The division result.
* @throws IllegalArgumentException if {@code value} is zero.
*/
public Rational divide(int value) {
return divide((long) value);
}
/**
* Returns a {@link Rational} whose value is {@code (this / value)}.
*
* @param value the value by which this {@link Rational} is to be divided.
* @return The division result.
* @throws IllegalArgumentException if {@code value} is zero.
*/
public Rational divide(long value) {
if (value == 0) {
throw new IllegalArgumentException("value cannot be zero/divison by zero");
}
if (numerator.signum() == 0 || value == 1) {
return this;
}
if (value == -1) {
return negate();
}
// Keep the sign in the numerator and the denominator positive
if (value < 0) {
return new Rational(reducedNumerator.negate(), reducedDenominator.multiply(BigInteger.valueOf(-value)));
}
return new Rational(reducedNumerator, reducedDenominator.multiply(BigInteger.valueOf(value)));
}
/**
* Returns a {@link Rational} whose value is {@code (this / value)}.
*
* @param value the value by which this {@link Rational} is to be divided.
* @return The division result.
* @throws IllegalArgumentException if {@code value} is zero.
*/
public Rational divide(BigInteger value) {
if (value == null || value.signum() == 0) {
throw new IllegalArgumentException("value cannot be zero/divison by zero");
}
if (numerator.signum() == 0) {
return this;
}
if (BigInteger.ONE.equals(value.abs())) {
return value.signum() < 0 ? negate() : this;
}
// Keep the sign in the numerator and the denominator positive
if (value.signum() < 0) {
return new Rational(reducedNumerator.negate(), reducedDenominator.multiply(value.negate()));
}
return new Rational(reducedNumerator, reducedDenominator.multiply(value));
}
/**
* Returns a {@link Rational} whose value is {@code (this / value)}.
*
* @param value the value by which this {@link Rational} is to be divided.
* @return The division result.
* @throws IllegalArgumentException if {@code value} is zero.
*/
public Rational divide(double value) {
if (value == 0) {
throw new IllegalArgumentException("value cannot be zero/divison by zero");
}
if (numerator.signum() == 0 || value == 1) {
return this;
}
if (value == -1) {
return negate();
}
return divide(new Rational(value));
}
/**
* Returns a {@link Rational} whose value is {@code (this / value)}.
*
* @param value the value by which this {@link Rational} is to be divided.
* @return The division result.
* @throws IllegalArgumentException if {@code value} is zero.
*/
public Rational divide(BigDecimal value) {
if (value == null || value.signum() == 0) {
throw new IllegalArgumentException("value cannot be zero/divison by zero");
}
if (numerator.signum() == 0 || BigDecimal.ONE.equals(value.abs())) {
return value.signum() < 0 ? negate() : this;
}
return divide(new Rational(value));
}
/**
* Returns a {@link Rational} whose value is {@code (this / value)}.
*
* @param value the value by which this {@link Rational} is to be divided.
* @return The division result.
* @throws IllegalArgumentException if {@code value} is zero.
*/
public Rational divide(Rational value) {
if (value == null || value.numerator.signum() == 0) {
throw new IllegalArgumentException("value cannot be zero/divison by zero");
}
return numerator.signum() == 0 ? this : multiply(value.reciprocal());
}
/**
* Returns a {@link Rational} whose value is {@code (-this)}.
*
* @return The negated result.
*/
public Rational negate() {
if (numerator.signum() == 0) {
return this;
}
return new Rational(numerator.negate(), denominator);
}
/**
* Returns a {@link Rational} whose value is the absolute value of this
* {@code (abs(this))}.
*
* @return The absolute result.
*/
public Rational abs() {
return numerator.signum() < 0 ? new Rational(numerator.negate(), denominator) : this;
}
/**
* Returns a {@link Rational} whose value is
* <tt>(this<sup>exponent</sup>)</tt>.
*
* @param exponent exponent to which this {@link Rational} is to be raised.
* @return <tt>this<sup>exponent</sup></tt>
*/
public Rational pow(int exponent) {
if (exponent == 0) {
return ONE;
}
if (exponent == 1) {
return this;
}
if (exponent < 0) {
if (exponent == Integer.MIN_VALUE) {
return this.reciprocal().pow(2).pow(-(exponent / 2));
}
return this.reciprocal().pow(-exponent);
}
Rational result = multiply(this);
if ((exponent % 2) == 0) {
return result.pow(exponent / 2);
}
return result.pow(exponent / 2).multiply(this);
}
/**
* Returns the signum function of this {@link Rational}.
*
* @return -1, 0 or 1 as the value of this {@link Rational} is negative,
* zero or positive.
*/
public int signum() {
return numerator.signum();
}
/**
* @return Whether the value of this {@link Rational} can be expressed as an
* integer value.
*/
public boolean isInteger() {
return BigInteger.ONE.equals(reducedDenominator);
}
// Getters
/**
* @return The numerator.
*/
public BigInteger getNumerator() {
return numerator;
}
/**
* @return The denominator.
*/
public BigInteger getDenominator() {
return denominator;
}
/**
* The reduced numerator is the numerator divided by
* {@link #getGreatestCommonDivisor}.
*
* @return The reduced numerator.
*/
public BigInteger getReducedNumerator() {
return reducedNumerator;
}
/**
* The reduced denominator is the denominator divided by
* {@link #getGreatestCommonDivisor}.
*
* @return The reduced denominator.
*/
public BigInteger getReducedDenominator() {
return reducedDenominator;
}
/**
* @return the greatest common divisor of the numerator and the denominator.
*/
public BigInteger getGreatestCommonDivisor() {
return greatestCommonDivisor;
}
// String methods
/**
* Returns a string representation of this {@link Rational} in it's rational
* form {@code (1/2, 4 or 16/9)}.
*
* @return The {@link String} representation.
*/
@Override
public String toString() {
return stringValue;
}
/**
* Returns a string representation of this {@link Rational} in it's reduced
* rational form {@code (1/2, 4 or 16/9)}. The reduced form is when both
* numerator and denominator have been divided by the greatest common
* divisor.
*
* @return The reduced {@link String} representation.
*/
public String toReducedString() {
return reducedStringValue;
}
/**
* Returns a decimal representation of this {@link Rational}. The decimal
* representation is limited to 20 decimals using
* {@link RoundingMode#HALF_EVEN} and is formatted with {@link Locale#ROOT}
* without grouping.
*
* @return The decimal {@link String} representation.
*/
public String toDecimalString() {
return decimalStringValue;
}
/**
* Returns a debug string representation of this {@link Rational}. The debug
* representation is a combination of {@link #toString},
* {@link #toReducedString} and {@link #toDecimalString}.
*
* @return The debug {@link String} representation.
*/
public String toDebugString() {
StringBuilder sb = new StringBuilder("Value: ");
sb.append(stringValue).append(", Reduced: ").append(reducedStringValue).append(", Decimal: ").append(decimalStringValue);
return sb.toString();
}
/**
* Returns a hexadecimal string representation of this {@link Rational} in
* it's rational form {@code (a/2, ff or 16/c)}.
*
* @return The hexadecimal {@link String} representation.
*/
public String toHexString() {
return generateRationalHexString(numerator, denominator);
}
/**
* Returns a hexadecimal string representation of this {@link Rational} in it's reduced
* rational form {@code (5, ff or 16/c)}. The reduced form is when both
* numerator and denominator have been divided by the greatest common
* divisor.
*
* @return The reduced hexadecimal {@link String} representation.
*/
public String toReducedHexString() {
return generateRationalHexString(reducedNumerator, reducedDenominator);
}
// Conversion to other Number formats
/**
* Converts this {@link Rational} to an {@code int}. This conversion is
* analogous to the <i>narrowing primitive conversion</i> from
* {@code double} to {@code int} as defined in section 5.1.3 of <cite>The
* Java™ Language Specification</cite>: any fractional part of this
* {@link Rational} will be discarded, and if the resulting "
* {@link BigInteger}" is too big to fit in an {@code int}, only the
* low-order 32 bits are returned.
* <p>
* Note that this conversion can lose information about the overall
* magnitude and precision of this {@link Rational} value as well as return
* a result with the opposite sign.
*
* @return This {@link Rational} converted to an {@code int}.
*/
@Override
public int intValue() {
return new BigDecimal(reducedNumerator).divide(new BigDecimal(reducedDenominator), RoundingMode.DOWN).intValue();
}
/**
*
* Converts this {@link Rational} to a {@code long}. This conversion is
* analogous to the <i>narrowing primitive conversion</i> from
* {@code double} to {@code long} as defined in section 5.1.3 of <cite>The
* Java™ Language Specification</cite>: any fractional part of this
* {@link Rational} will be discarded, and if the resulting "
* {@link BigInteger}" is too big to fit in a {@code long}, only the
* low-order 64 bits are returned.
* <p>
* Note that this conversion can lose information about the overall
* magnitude and precision of this {@link Rational} value as well as return
* a result with the opposite sign.
*
* @return This {@link Rational} converted to a {@code long}.
*/
@Override
public long longValue() {
return new BigDecimal(reducedNumerator).divide(new BigDecimal(reducedDenominator), RoundingMode.DOWN).longValue();
}
/**
* Converts this {@link Rational} to a {@link BigInteger}. This conversion
* is analogous to the <i>narrowing primitive conversion</i> from
* {@code double} to {@code long} as defined in section 5.1.3 of <cite>The
* Java™ Language Specification</cite>: any fractional part of this
* {@link Rational} will be discarded.
*
* @return This {@link Rational} converted to a {@link BigInteger}.
*/
public BigInteger bigIntegerValue() {
return new BigDecimal(reducedNumerator).divide(new BigDecimal(reducedDenominator), RoundingMode.DOWN).toBigInteger();
}
/**
* Converts this {@link Rational} to a {@code float}. This conversion is
* similar to the <i>narrowing primitive conversion</i> from {@code double}
* to {@code float} as defined in section 5.1.3 of <cite>The Java™
* Language Specification</cite>: if this {@link Rational} has too great a
* magnitude to represent as a {@code float}, it will be converted to
* {@link Float#NEGATIVE_INFINITY} or {@link Float#POSITIVE_INFINITY} as
* appropriate.
* <p>
* Note that even when the return value is finite, this conversion can lose
* information about the precision of the {@link Rational} value.
*
* @return This {@link Rational} converted to a {@code float}.
*/
@Override
public float floatValue() {
return new BigDecimal(reducedNumerator).divide(new BigDecimal(reducedDenominator), MathContext.DECIMAL32).floatValue();
}
/**
* Converts this {@link Rational} to a {@code double}. This conversion is
* similar to the <i>narrowing primitive conversion</i> from {@code double}
* to {@code float} as defined in section 5.1.3 of <cite>The Java™
* Language Specification</cite>: if this {@link Rational} has too great a
* magnitude to represent as a {@code double}, it will be converted to
* {@link Double#NEGATIVE_INFINITY} or {@link Double#POSITIVE_INFINITY} as
* appropriate.
* <p>
* Note that even when the return value is finite, this conversion can lose
* information about the precision of the {@link Rational} value.
*
* @return This {@link Rational} converted to a {@code double}.
*/
@Override
public double doubleValue() {
return new BigDecimal(reducedNumerator).divide(new BigDecimal(reducedDenominator), MathContext.DECIMAL64).doubleValue();
}
/**
* Converts this {@link Rational} to a {@link BigDecimal}. This may involve
* rounding. The conversion is limited to 100 decimals and uses
* {@link RoundingMode#HALF_EVEN}.
* <p>
* For explicit control over the conversion, use one of the overloaded
* methods.
*
* @see #bigDecimalValue(MathContext)
* @see #bigDecimalValue(RoundingMode)
* @see #bigDecimalValue(int, RoundingMode)
*
* @return This {@link Rational} converted to a {@link BigDecimal}.
*/
public BigDecimal bigDecimalValue() {
if (BigInteger.ONE.equals(reducedDenominator)) {
return new BigDecimal(reducedNumerator);
}
return new BigDecimal(reducedNumerator).divide(new BigDecimal(reducedDenominator), 100, RoundingMode.HALF_EVEN);
}
/**
* Converts this {@link Rational} to a {@link BigDecimal}. This may involve
* rounding. The conversion is limited to 100 decimals and uses
* {@code roundingMode}.
*
* @see #bigDecimalValue()
* @see #bigDecimalValue(MathContext)
* @see #bigDecimalValue(int, RoundingMode)
*
* @param roundingMode the {@link RoundingMode} to apply.
* @return This {@link Rational} converted to a {@link BigDecimal}.
*/
public BigDecimal bigDecimalValue(RoundingMode roundingMode) {
if (BigInteger.ONE.equals(reducedDenominator)) {
return new BigDecimal(reducedNumerator);
}
return new BigDecimal(reducedNumerator).divide(new BigDecimal(reducedDenominator), 100, roundingMode);
}
/**
* Converts this {@link Rational} to a {@link BigDecimal}. This may involve
* rounding.
* <p>
* Use {@code scale == 0} and
* {@code roundingMode == RoundingMode.UNNECESSARY} to achieve absolute
* precision. This will throw an {@link ArithmeticException} if the exact
* quotient cannot be represented (because it has a non-terminating decimal
* expansion).
*
* @see #bigDecimalValue()
* @see #bigDecimalValue(MathContext)
* @see #bigDecimalValue(RoundingMode)
*
* @param scale the scale of the {@link BigDecimal} quotient to be returned.
* @param roundingMode the {@link RoundingMode} to apply.
* @return This {@link Rational} converted to a {@link BigDecimal}.
* @throws ArithmeticException If
* {@code roundingMode == RoundingMode.UNNECESSARY} and the
* specified scale is insufficient to represent the result of
* the division exactly.
*/
public BigDecimal bigDecimalValue(int scale, RoundingMode roundingMode) {
if (BigInteger.ONE.equals(reducedDenominator)) {
return new BigDecimal(reducedNumerator);
}
return new BigDecimal(reducedNumerator).divide(new BigDecimal(reducedDenominator), scale, roundingMode);
}
/**
* Converts this {@link Rational} to a {@link BigDecimal} using the given
* {@link MathContext}. This may involve rounding.
* <p>
*
* @see #bigDecimalValue()
* @see #bigDecimalValue(RoundingMode)
* @see #bigDecimalValue(int, RoundingMode)
*
* @param mathContext the {@link MathContext} to use.
* @return This {@link Rational} converted to a {@link BigDecimal}.
* @throws ArithmeticException If the result is inexact but the rounding
* mode is {@code UNNECESSARY} or
* {@code mathContext.precision == 0} and the quotient has a
* non-terminating decimal expansion.
*/
public BigDecimal bigDecimalValue(MathContext mathContext) {
if (BigInteger.ONE.equals(reducedDenominator)) {
return new BigDecimal(reducedNumerator);
}
return new BigDecimal(reducedNumerator).divide(new BigDecimal(reducedDenominator), mathContext);
}
// Comparison methods
/**
* Compares this {@link Rational} with {@code other}. Two {@link Rational}
* instances that are equal in value but have different numerators and
* denominators are considered equal by this methods (like {@code 1/2} and
* {@code 2/4}).
* <p>
* This method is provided in preference to individual methods for each of
* the six boolean comparison operators ({@literal <}, ==, {@literal >},
* {@literal >=}, !=, {@literal <=}). The suggested idiom for performing
* these comparisons is: {@code (x.compareTo(y)} <<i>op</i>>
* {@code 0)}, where <<i>op</i>> is one of the six comparison
* operators.
*
* @param other the {@link Rational} to which this {@link Rational} is to be
* compared.
* @return A negative integer, zero, or a positive integer as this
* {@link Rational} is numerically less than, equal to, or greater
* than {@code other}.
*/
@Override
public int compareTo(Rational other) {
if (signum() != other.signum()) {
return signum() - other.signum();
}
BigInteger[] multipliers = getMultipliers(other);
return reducedNumerator.multiply(multipliers[0]).compareTo(other.reducedNumerator.multiply(multipliers[1]));
}
/**
* Compares this {@link Rational} by value with a {@code int}.
* <p>
* This method is provided in preference to individual methods for each of
* the six boolean comparison operators ({@literal <}, ==, {@literal >},
* {@literal >=}, !=, {@literal <=}). The suggested idiom for performing
* these comparisons is: {@code (x.compareTo(y)} <<i>op</i>>
* {@code 0)}, where <<i>op</i>> is one of the six comparison
* operators.
*
* @param value the {@code int} to which this {@link Rational}'s value is to
* be compared.
* @return A negative integer, zero, or a positive integer as this
* {@link Rational} is numerically less than, equal to, or greater
* than {@code value}.
*/
public int compareTo(int value) {
return compareTo(Integer.valueOf(value));
}
/**
* Compares this {@link Rational} by value with a {@code long}.
* <p>
* This method is provided in preference to individual methods for each of
* the six boolean comparison operators ({@literal <}, ==, {@literal >},
* {@literal >=}, !=, {@literal <=}). The suggested idiom for performing
* these comparisons is: {@code (x.compareTo(y)} <<i>op</i>>
* {@code 0)}, where <<i>op</i>> is one of the six comparison
* operators.
*
* @param value the {@code long} to which this {@link Rational}'s value
* is to be compared.
* @return A negative integer, zero, or a positive integer as this
* {@link Rational} is numerically less than, equal to, or greater
* than {@code value}.
*/
public int compareTo(long value) {
return compareTo(Long.valueOf(value));
}
/**
* Compares this {@link Rational} by value with a {@code float}.
* <p>
* This method is provided in preference to individual methods for each of
* the six boolean comparison operators ({@literal <}, ==, {@literal >},
* {@literal >=}, !=, {@literal <=}). The suggested idiom for performing
* these comparisons is: {@code (x.compareTo(y)} <<i>op</i>>
* {@code 0)}, where <<i>op</i>> is one of the six comparison
* operators.
*
* @param value the {@code float} to which this {@link Rational}'s value
* is to be compared.
* @return A negative integer, zero, or a positive integer as this
* {@link Rational} is numerically less than, equal to, or greater
* than {@code value}.
*/
public int compareTo(float value) {
return compareTo(Float.valueOf(value));
}
/**
* Compares this {@link Rational} by value with a {@code double}.
* <p>
* This method is provided in preference to individual methods for each of
* the six boolean comparison operators ({@literal <}, ==, {@literal >},
* {@literal >=}, !=, {@literal <=}). The suggested idiom for performing
* these comparisons is: {@code (x.compareTo(y)} <<i>op</i>>
* {@code 0)}, where <<i>op</i>> is one of the six comparison
* operators.
*
* @param value the {@code double} to which this {@link Rational}'s value
* is to be compared.
* @return A negative integer, zero, or a positive integer as this
* {@link Rational} is numerically less than, equal to, or greater
* than {@code value}.
*/
public int compareTo(double value) {
return compareTo(Double.valueOf(value));
}
/**
* Compares this {@link Rational} by value with any class implementing
* {@link Number}.
* <p>
* This method is provided in preference to individual methods for each of
* the six boolean comparison operators ({@literal <}, ==, {@literal >},
* {@literal >=}, !=, {@literal <=}). The suggested idiom for performing
* these comparisons is: {@code (x.compareTo(y)} <<i>op</i>>
* {@code 0)}, where <<i>op</i>> is one of the six comparison
* operators.
*
* @param number the {@link Number} to which this {@link Rational}'s value
* is to be compared.
* @return A negative integer, zero, or a positive integer as this
* {@link Rational} is numerically less than, equal to, or greater
* than {@code number}.
*/
public int compareTo(Number number) {
// List known integer types for faster and more accurate comparison
if (number instanceof BigInteger) {
if (isInteger()) {
return bigIntegerValue().compareTo((BigInteger) number);
}
return bigDecimalValue(2, RoundingMode.HALF_EVEN).compareTo(new BigDecimal((BigInteger) number));
}
if (
number instanceof AtomicInteger ||
number instanceof AtomicLong ||
number instanceof Byte ||
number instanceof Integer ||
number instanceof Long ||
number instanceof Short
) {
if (isInteger()) {
return bigIntegerValue().compareTo(BigInteger.valueOf(number.longValue()));
}
return bigDecimalValue(2, RoundingMode.HALF_EVEN).compareTo(new BigDecimal(number.longValue()));
}
if (number instanceof BigDecimal) {
Rational other = new Rational((BigDecimal) number);
return compareTo(other);
}
return bigDecimalValue().compareTo(new BigDecimal(number.doubleValue()));
}
@Override
public int hashCode() {
return hashCode;
}
/**
* Used internally to calculate the {@code hashCode} for caching.
*
* @return The calculated {@code hashCode}.
*/
protected int calculateHashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((reducedDenominator == null) ? 0 : reducedDenominator.hashCode());
result = prime * result + ((reducedNumerator == null) ? 0 : reducedNumerator.hashCode());
return result;
}
/**
* Indicates whether this instance and {@code object} are mathematically
* equivalent, given that {@code other} implements {@link Number}.
* <p>
* 1/2 and 4/8 are considered equal by this method, as are 4/2 and 2.
* <p>
* Equality is determined by equality of {@code reducedNumerator} and
* {@code reducedDenominator} if {@code object} is another {@link Rational}.
* If not, but {@code object} implements {@link Number},
* {@code compareTo(Number) == 0} is used to determine equality.
* <p>
* To test equal representation, use {@link #equalsExact}.
*
* @param object the reference object with which to compare.
* @return {@code true} if {@code object} is a {@link Rational} and are
* mathematically equivalent, {@code false} otherwise.
*/
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null) {
return false;
}
if (!(object instanceof Number)) {
return false;
}
if (object instanceof Rational) {
Rational other = (Rational) object;
if (reducedDenominator == null) {
if (other.reducedDenominator != null) {
return false;
}
} else if (!reducedDenominator.equals(other.reducedDenominator)) {
return false;
}
if (reducedNumerator == null) {
if (other.reducedNumerator != null) {
return false;
}
} else if (!reducedNumerator.equals(other.reducedNumerator)) {
return false;
}
} else {
if (reducedNumerator == null || reducedDenominator == null) {
// Should be impossible
return false;
}
return compareTo((Number) object) == 0;
}
return true;
}
/**
* Indicates whether this instance and {@code other} have identical
* numerator and denominator.
* <p>
* 1/2, 4/8, 4/2 and 2 are all considered non-equal by this method.
* <p>
* To test mathematically equivalence, use {@link #equals}.
*
* @param other the {@link Rational} with which to compare.
* @return {@code true} if this instance and {@code other} have an identical
* representation.
*/
public boolean equalsExact(Rational other) {
return other != null && numerator.equals(other.numerator) && denominator.equals(other.denominator);
}
/**
* Used internally to find by which factor to multiply the reduced
* numerators when comparing two {@link Rational}s.
*
* @param other the {@link Rational} to which this {@link Rational}'s value
* is to be compared.
* @return An array of {@link BigInteger} multipliers.
*/
protected BigInteger[] getMultipliers(Rational other) {
BigInteger[] result = new BigInteger[2];
BigInteger lcm = calculateLeastCommonMultiple(reducedDenominator, other.reducedDenominator);
result[0] = lcm.divide(reducedDenominator);
result[1] = lcm.divide(other.reducedDenominator);
return result;
}
// Static methods
/**
* Calculates the greatest common divisor for two {@link Long}s using
* "binary GDC" with some optimizations borrowed from
* {@link org.apache.commons.lang.math.Fraction#greatestCommonDivisor}.
*
* @param u the first number.
* @param v the second number.
* @return The GDC, always 1 or greater.
*/
public static long calculateGreatestCommonDivisor(long u, long v) {
if (Math.abs(u) <= 1 || Math.abs(v) <= 1) {
return 1;
}
// keep u and v negative, as negative integers range down to
// -2^63, while positive numbers can only be as large as 2^63-1.
if (u > 0) {
u = -u;
}
if (v > 0) {
v = -v;
}
int k = 0;
while ((u & 1) == 0 && (v & 1) == 0 && k < 63) {
u >>= 1;
v >>= 1;
k++;
}
if (k == 63) {
throw new ArithmeticException("Overflow: gcd is 2^63");
}
long t = ((u & 1) == 1) ? v : -(u >> 1);
do {
while ((t & 1) == 0) {
t >>= 1;
}
if (t > 0) {
u = -t;
} else {
v = t;
}
t = (v - u) >> 1;
} while (t != 0);
return -u * (1 << k);
}
/**
* Calculates the greatest common divisor for two {@link BigInteger}s using
* {@link BigInteger#gcd}.
*
* @param u the first number.
* @param v the second number.
* @return The GDC, always 1 or greater.
*/
public static BigInteger calculateGreatestCommonDivisor(BigInteger u, BigInteger v) {
if (u.abs().compareTo(BigInteger.ONE) <= 0 || v.abs().compareTo(BigInteger.ONE) <= 0) {
return BigInteger.ONE;
}
return u.gcd(v);
}
/**
* Calculates the least common multiple for two {@link BigInteger}s using the formula
* {@code u * v / gcd(u, v)} where {@code gcd} is the greatest common divisor for the two.
*
* @param u the first number.
* @param v the second number.
* @return The LCM, always 1 or greater.
*/
public static BigInteger calculateLeastCommonMultiple(BigInteger u, BigInteger v) {
if (u == null) {
u = BigInteger.ZERO;
}
if (v == null) {
v = BigInteger.ZERO;
}
if (u.signum() == 0 && v.signum() == 0) {
return BigInteger.ONE;
}
u = u.abs();
v = v.abs();
if (u.signum() == 0) {
return v;
}
if (v.signum() == 0) {
return u;
}
return u.divide(calculateGreatestCommonDivisor(u, v)).multiply(v);
}
/**
* Used internally to generate a hexadecimal rational string representation
* from two {@link BigInteger}s.
*
* @param numerator the numerator.
* @param denominator the denominator.
* @return The hexadecimal rational string representation.
*/
protected static String generateRationalHexString(BigInteger numerator, BigInteger denominator) {
if (BigInteger.ONE.equals(denominator)) {
return numerator.toString(16);
}
return numerator.toString(16) + "/" + denominator.toString(16);
}
/**
* Used internally to generate a rational string representation from two
* {@link BigInteger}s.
*
* @param numerator the numerator.
* @param denominator the denominator.
* @return The rational string representation.
*/
protected static String generateRationalString(BigInteger numerator, BigInteger denominator) {
if (BigInteger.ONE.equals(denominator)) {
return numerator.toString();
}
return numerator.toString() + "/" + denominator.toString();
}
/**
* Used internally to generate a decimal string representation from two
* {@link BigInteger}s. The decimal representation is limited to 20
* decimals.
*
* @param numerator the numerator.
* @param denominator the denominator.
* @return The string representation.
*/
protected static String generateDecimalString(BigInteger numerator, BigInteger denominator) {
if (BigInteger.ONE.equals(denominator)) {
return numerator.toString();
}
BigDecimal decimalValue = new BigDecimal(numerator).divide(new BigDecimal(denominator), 20, RoundingMode.HALF_EVEN);
return DECIMALFORMAT.format(decimalValue);
}
}