我整理了cletus' answer:
- 为所有方法添加了Javadoc。
- 添加了对方法前提条件的检查。
- 用更灵活和更快的
BigInteger(String)
替换了valueOf(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;
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);
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);
}
public static BigRational valueOf(BigInteger numerator, BigInteger denominator)
{
Preconditions.requireThat(numerator, "numerator").isNotNull();
Preconditions.requireThat(denominator, "denominator").isNotNull();
return canonical(numerator, denominator, true);
}
public static BigRational valueOf(long numerator, long denominator)
{
BigInteger bigNumerator = BigInteger.valueOf(numerator);
BigInteger bigDenominator = BigInteger.valueOf(denominator);
return canonical(bigNumerator, bigDenominator, true);
}
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);
}
}
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);
}
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);
}
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;
private BigRational(BigInteger numerator, BigInteger denominator)
{
Preconditions.requireThat(numerator, "numerator").isNotNull();
Preconditions.requireThat(denominator, "denominator").isNotNull();
this.numerator = numerator;
this.denominator = denominator;
}
public BigInteger getNumerator()
{
return numerator;
}
public BigInteger getDenominator()
{
return denominator;
}
@Override
@SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
public int compareTo(BigRational other)
{
Preconditions.requireThat(other, "other").isNotNull();
if (numerator.signum() != other.numerator.signum())
return numerator.signum() - other.numerator.signum();
BigInteger first = numerator.multiply(other.denominator);
BigInteger second = other.numerator.multiply(denominator);
return first.compareTo(second);
}
@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);
}
@SuppressWarnings("AccessingNonPublicFieldOfAnotherObject")
public BigRational subtract(BigRational other)
{
return add(other.negate());
}
@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);
}
public BigRational divide(BigRational other)
{
return multiply(other.invert());
}
public boolean isInteger()
{
return numerator.signum() == 0 || denominator.equals(BigInteger.ONE);
}
public BigRational negate()
{
return new BigRational(numerator.negate(), denominator);
}
public BigRational invert()
{
return canonical(denominator, numerator, false);
}
public BigRational abs()
{
if (numerator.signum() < 0)
return negate();
return this;
}
public BigRational pow(int exponent)
{
return canonical(numerator.pow(exponent), denominator.pow(exponent), true);
}
public BigRational min(BigRational other)
{
if (compareTo(other) <= 0)
return this;
return other;
}
public BigRational max(BigRational other)
{
if (compareTo(other) >= 0)
return this;
return other;
}
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);
}
@Override
public String toString()
{
if (isInteger())
return String.format("%,d", numerator);
return String.format("%,d / %,d", numerator, denominator);
}
}
compareTo
中使用long
以防止溢出,但乘法仅使用int
。 - maaartinushashCode
。仅定义equals
可能会导致哈希映射中出现非常糟糕的行为(比两者都不定义还要糟糕)。请注意,定义hashCode
需要某种规范化以保持与equals
一致。 - maaartinus