Я убрал Ответ Клетуса :
- Добавлен Javadoc для всех методов.
- Добавлены проверки предварительных условий метода.
- Заменен пользовательский анализ в
valueOf(String)
на BigInteger(String)
, который стал более гибким и быстрым.
import com.google.common.base.Splitter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.List;
import java.util.Objects;
import org.bitbucket.cowwoc.preconditions.Preconditions;
/**
* A rational fraction, represented by {@code numerator / denominator}.
* <p>
* This implementation is based on <a
* href="https://stackoverflow.com/a/474577/14731">https://stackoverflow.com/a/474577/14731</a>
* <p>
* @author Gili Tzabari
*/
public final class BigRational extends Number implements Comparable<BigRational>
{
private static final long serialVersionUID = 0L;
public static final BigRational ZERO = new BigRational(BigInteger.ZERO, BigInteger.ONE);
public static final BigRational ONE = new BigRational(BigInteger.ONE, BigInteger.ONE);
/**
* Ensures the fraction the denominator is positive and optionally divides the numerator and
* denominator by the greatest common factor.
* <p>
* @param numerator a numerator
* @param denominator a denominator
* @param checkGcd true if the numerator and denominator should be divided by the greatest
* common factor
* @return the canonical representation of the rational fraction
*/
private static BigRational canonical(BigInteger numerator, BigInteger denominator,
boolean checkGcd)
{
assert (numerator != null);
assert (denominator != null);
if (denominator.signum() == 0)
throw new IllegalArgumentException("denominator is zero");
if (numerator.signum() == 0)
return ZERO;
BigInteger newNumerator = numerator;
BigInteger newDenominator = denominator;
if (newDenominator.signum() < 0)
{
newNumerator = newNumerator.negate();
newDenominator = newDenominator.negate();
}
if (checkGcd)
{
BigInteger gcd = newNumerator.gcd(newDenominator);
if (!gcd.equals(BigInteger.ONE))
{
newNumerator = newNumerator.divide(gcd);
newDenominator = newDenominator.divide(gcd);
}
}
return new BigRational(newNumerator, newDenominator);
}
/**
* @param numerator a numerator
* @param denominator a denominator
* @return a BigRational having value {@code numerator / denominator}
* @throws NullPointerException if numerator or denominator are null
*/
public static BigRational valueOf(BigInteger numerator, BigInteger denominator)
{
Preconditions.requireThat(numerator, "numerator").isNotNull();
Preconditions.requireThat(denominator, "denominator").isNotNull();
return canonical(numerator, denominator, true);
}
/**
* @param numerator a numerator
* @param denominator a denominator
* @return a BigRational having value {@code numerator / denominator}
*/
public static BigRational valueOf(long numerator, long denominator)
{
BigInteger bigNumerator = BigInteger.valueOf(numerator);
BigInteger bigDenominator = BigInteger.valueOf(denominator);
return canonical(bigNumerator, bigDenominator, true);
}
/**
* @param value the parameter value
* @param name the parameter name
* @return the BigInteger representation of the parameter
* @throws NumberFormatException if value is not a valid representation of BigInteger
*/
private static BigInteger requireBigInteger(String value, String name)
throws NumberFormatException
{
try
{
return new BigInteger(value);
}
catch (NumberFormatException e)
{
throw (NumberFormatException) new NumberFormatException("Invalid " + name + ": " + value).
initCause(e);
}
}
/**
* @param numerator a numerator
* @param denominator a denominator
* @return a BigRational having value {@code numerator / denominator}
* @throws NullPointerException if numerator or denominator are null
* @throws IllegalArgumentException if numerator or denominator are empty
* @throws NumberFormatException if numerator or denominator are not a valid representation of
* BigDecimal
*/
public static BigRational valueOf(String numerator, String denominator)
throws NullPointerException, IllegalArgumentException, NumberFormatException
{
Preconditions.requireThat(numerator, "numerator").isNotNull().isNotEmpty();
Preconditions.requireThat(denominator, "denominator").isNotNull().isNotEmpty();
BigInteger bigNumerator = requireBigInteger(numerator, "numerator");
BigInteger bigDenominator = requireBigInteger(denominator, "denominator");
return canonical(bigNumerator, bigDenominator, true);
}
/**
* @param value a string representation of a rational fraction (e.g. "12.34e5" or "3/4")
* @return a BigRational representation of the String
* @throws NullPointerException if value is null
* @throws IllegalArgumentException if value is empty
* @throws NumberFormatException if numerator or denominator are not a valid representation of
* BigDecimal
*/
public static BigRational valueOf(String value)
throws NullPointerException, IllegalArgumentException, NumberFormatException
{
Preconditions.requireThat(value, "value").isNotNull().isNotEmpty();
List<String> fractionParts = Splitter.on('/').splitToList(value);
if (fractionParts.size() == 1)
return valueOfRational(value);
if (fractionParts.size() == 2)
return BigRational.valueOf(fractionParts.get(0), fractionParts.get(1));
throw new IllegalArgumentException("Too many slashes: " + value);
}
/**
* @param value a string representation of a rational fraction (e.g. "12.34e5")
* @return a BigRational representation of the String
* @throws NullPointerException if value is null
* @throws IllegalArgumentException if value is empty
* @throws NumberFormatException if numerator or denominator are not a valid representation of
* BigDecimal
*/
private static BigRational valueOfRational(String value)
throws NullPointerException, IllegalArgumentException, NumberFormatException
{
Preconditions.requireThat(value, "value").isNotNull().isNotEmpty();
BigDecimal bigDecimal = new BigDecimal(value);
int scale = bigDecimal.scale();
BigInteger numerator = bigDecimal.unscaledValue();
BigInteger denominator;
if (scale > 0)
denominator = BigInteger.TEN.pow(scale);
else
{
numerator = numerator.multiply(BigInteger.TEN.pow(-scale));
denominator = BigInteger.ONE;
}
return canonical(numerator, denominator, true);
}
private final BigInteger numerator;
private final BigInteger denominator;
/**
* @param numerator the numerator
* @param denominator the denominator
* @throws NullPointerException if numerator or denominator are null
*/
private BigRational(BigInteger numerator, BigInteger denominator)
{
Preconditions.requireThat(numerator, "numerator").isNotNull();
Preconditions.requireThat(denominator, "denominator").isNotNull();
this.numerator = numerator;
this.denominator = denominator;
}
/**
* @return the numerator
*/
public BigInteger getNumerator()
{
return numerator;
}
/**
* @return the denominator
*/
public BigInteger getDenominator()
{
return denominator;
}
@Override
@SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
public int compareTo(BigRational other)
{
Preconditions.requireThat(other, "other").isNotNull();
// canonical() ensures denominator is positive
if (numerator.signum() != other.numerator.signum())
return numerator.signum() - other.numerator.signum();
// Set the denominator to a common multiple before comparing the numerators
BigInteger first = numerator.multiply(other.denominator);
BigInteger second = other.numerator.multiply(denominator);
return first.compareTo(second);
}
/**
* @param other another rational fraction
* @return the result of adding this object to {@code other}
* @throws NullPointerException if other is null
*/
@SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
public BigRational add(BigRational other)
{
Preconditions.requireThat(other, "other").isNotNull();
if (other.numerator.signum() == 0)
return this;
if (numerator.signum() == 0)
return other;
if (denominator.equals(other.denominator))
return new BigRational(numerator.add(other.numerator), denominator);
return canonical(numerator.multiply(other.denominator).
add(other.numerator.multiply(denominator)),
denominator.multiply(other.denominator), true);
}
/**
* @param other another rational fraction
* @return the result of subtracting {@code other} from this object
* @throws NullPointerException if other is null
*/
@SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
public BigRational subtract(BigRational other)
{
return add(other.negate());
}
/**
* @param other another rational fraction
* @return the result of multiplying this object by {@code other}
* @throws NullPointerException if other is null
*/
@SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
public BigRational multiply(BigRational other)
{
Preconditions.requireThat(other, "other").isNotNull();
if (numerator.signum() == 0 || other.numerator.signum() == 0)
return ZERO;
if (numerator.equals(other.denominator))
return canonical(other.numerator, denominator, true);
if (other.numerator.equals(denominator))
return canonical(numerator, other.denominator, true);
if (numerator.negate().equals(other.denominator))
return canonical(other.numerator.negate(), denominator, true);
if (other.numerator.negate().equals(denominator))
return canonical(numerator.negate(), other.denominator, true);
return canonical(numerator.multiply(other.numerator), denominator.multiply(other.denominator),
true);
}
/**
* @param other another rational fraction
* @return the result of dividing this object by {@code other}
* @throws NullPointerException if other is null
*/
public BigRational divide(BigRational other)
{
return multiply(other.invert());
}
/**
* @return true if the object is a whole number
*/
public boolean isInteger()
{
return numerator.signum() == 0 || denominator.equals(BigInteger.ONE);
}
/**
* Returns a BigRational whose value is (-this).
* <p>
* @return -this
*/
public BigRational negate()
{
return new BigRational(numerator.negate(), denominator);
}
/**
* @return a rational fraction with the numerator and denominator swapped
*/
public BigRational invert()
{
return canonical(denominator, numerator, false);
}
/**
* @return the absolute value of this {@code BigRational}
*/
public BigRational abs()
{
if (numerator.signum() < 0)
return negate();
return this;
}
/**
* @param exponent exponent to which both numerator and denominator is to be raised.
* @return a BigRational whose value is (this<sup>exponent</sup>).
*/
public BigRational pow(int exponent)
{
return canonical(numerator.pow(exponent), denominator.pow(exponent), true);
}
/**
* @param other another rational fraction
* @return the minimum of this object and the other fraction
*/
public BigRational min(BigRational other)
{
if (compareTo(other) <= 0)
return this;
return other;
}
/**
* @param other another rational fraction
* @return the maximum of this object and the other fraction
*/
public BigRational max(BigRational other)
{
if (compareTo(other) >= 0)
return this;
return other;
}
/**
* @param scale scale of the BigDecimal quotient to be returned
* @param roundingMode the rounding mode to apply
* @return a BigDecimal representation of this object
* @throws NullPointerException if roundingMode is null
*/
public BigDecimal toBigDecimal(int scale, RoundingMode roundingMode)
{
Preconditions.requireThat(roundingMode, "roundingMode").isNotNull();
if (isInteger())
return new BigDecimal(numerator);
return new BigDecimal(numerator).divide(new BigDecimal(denominator), scale, roundingMode);
}
@Override
public int intValue()
{
return (int) longValue();
}
@Override
public long longValue()
{
if (isInteger())
return numerator.longValue();
return numerator.divide(denominator).longValue();
}
@Override
public float floatValue()
{
return (float) doubleValue();
}
@Override
public double doubleValue()
{
if (isInteger())
return numerator.doubleValue();
return numerator.doubleValue() / denominator.doubleValue();
}
@Override
@SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
public boolean equals(Object o)
{
if (this == o)
return true;
if (!(o instanceof BigRational))
return false;
BigRational other = (BigRational) o;
return numerator.equals(other.denominator) && Objects.equals(denominator, other.denominator);
}
@Override
public int hashCode()
{
return Objects.hash(numerator, denominator);
}
/**
* Returns the String representation: {@code numerator / denominator}.
*/
@Override
public String toString()
{
if (isInteger())
return String.format("%,d", numerator);
return String.format("%,d / %,d", numerator, denominator);
}
}