表示Java分数的最佳方法?
我正在尝试使用Java中的分数。
我想实现算术函数。为此,我首先需要一种将功能标准化的方法。我知道我只有一个公分母才能将1/6和1/2加起来。我将不得不添加1/6和3/6。天真的方法会让我加2/12和6/12,然后减少。如何以最少的性能损失获得一个共同的分母?哪种算法最适合呢?
版本8(感谢hstoerr):
改进包括:
现在equals()方法与compareTo()方法一致
final class Fraction extends Number { private int numerator;
private int denominator;
public Fraction(int numerator, int denominator) {
if(denominator == 0) {
throw new IllegalArgumentException("denominator is zero");
}
if(denominator < 0) {
numerator *= -1;
denominator *= -1;
}
this.numerator = numerator;
this.denominator = denominator;
}
public Fraction(int numerator) {
this.numerator = numerator;
this.denominator = 1;
}
public int getNumerator() {
return this.numerator;
}
public int getDenominator() {
return this.denominator;
}
public byte byteValue() {
return (byte) this.doubleValue();
}
public double doubleValue() {
return ((double) numerator)/((double) denominator);
}
public float floatValue() {
return (float) this.doubleValue();
}
public int intValue() {
return (int) this.doubleValue();
}
public long longValue() {
return (long) this.doubleValue();
}
public short shortValue() {
return (short) this.doubleValue();
}
public boolean equals(Fraction frac) {
return this.compareTo(frac) == 0;
}
public int compareTo(Fraction frac) {
long t = this.getNumerator() * frac.getDenominator();
long f = frac.getNumerator() * this.getDenominator();
int result = 0;
if(t>f) {
result = 1;
}
else if(f>t) {
result = -1;
}
return result;
}
}
回答:
碰巧的是不久前我写了一个BigFraction类,用于解决Euler项目问题。它保留了BigInteger分子和分母,因此它将永远不会溢出。但是,对于许多你永远不会溢出的操作来说,这会有点慢。无论如何,请根据需要使用它。我一直很想以某种方式炫耀它。:)
编辑:该代码的最新,最出色的版本,包括单元测试,现在托管在GitHub上,也可以通过Maven Central获得。我将原始代码留在这里,以便此答案不仅仅是一个链接…
import java.math.*;/**
* Arbitrary-precision fractions, utilizing BigIntegers for numerator and
* denominator. Fraction is always kept in lowest terms. Fraction is
* immutable, and guaranteed not to have a null numerator or denominator.
* Denominator will always be positive (so sign is carried by numerator,
* and a zero-denominator is impossible).
*/
public final class BigFraction extends Number implements Comparable<BigFraction>
{
private static final long serialVersionUID = 1L; //because Number is Serializable
private final BigInteger numerator;
private final BigInteger denominator;
public final static BigFraction ZERO = new BigFraction(BigInteger.ZERO, BigInteger.ONE, true);
public final static BigFraction ONE = new BigFraction(BigInteger.ONE, BigInteger.ONE, true);
/**
* Constructs a BigFraction with given numerator and denominator. Fraction
* will be reduced to lowest terms. If fraction is negative, negative sign will
* be carried on numerator, regardless of how the values were passed in.
*/
public BigFraction(BigInteger numerator, BigInteger denominator)
{
if(numerator == null)
throw new IllegalArgumentException("Numerator is null");
if(denominator == null)
throw new IllegalArgumentException("Denominator is null");
if(denominator.equals(BigInteger.ZERO))
throw new ArithmeticException("Divide by zero.");
//only numerator should be negative.
if(denominator.signum() < 0)
{
numerator = numerator.negate();
denominator = denominator.negate();
}
//create a reduced fraction
BigInteger gcd = numerator.gcd(denominator);
this.numerator = numerator.divide(gcd);
this.denominator = denominator.divide(gcd);
}
/**
* Constructs a BigFraction from a whole number.
*/
public BigFraction(BigInteger numerator)
{
this(numerator, BigInteger.ONE, true);
}
public BigFraction(long numerator, long denominator)
{
this(BigInteger.valueOf(numerator), BigInteger.valueOf(denominator));
}
public BigFraction(long numerator)
{
this(BigInteger.valueOf(numerator), BigInteger.ONE, true);
}
/**
* Constructs a BigFraction from a floating-point number.
*
* Warning: round-off error in IEEE floating point numbers can result
* in answers that are unexpected. For example,
* System.out.println(new BigFraction(1.1))
* will print:
* 2476979795053773/2251799813685248
*
* This is because 1.1 cannot be expressed exactly in binary form. The
* given fraction is exactly equal to the internal representation of
* the double-precision floating-point number. (Which, for 1.1, is:
* (-1)^0 * 2^0 * (1 + 0x199999999999aL / 0x10000000000000L).)
*
* NOTE: In many cases, BigFraction(Double.toString(d)) may give a result
* closer to what the user expects.
*/
public BigFraction(double d)
{
if(Double.isInfinite(d))
throw new IllegalArgumentException("double val is infinite");
if(Double.isNaN(d))
throw new IllegalArgumentException("double val is NaN");
//special case - math below won't work right for 0.0 or -0.0
if(d == 0)
{
numerator = BigInteger.ZERO;
denominator = BigInteger.ONE;
return;
}
final long bits = Double.doubleToLongBits(d);
final int sign = (int)(bits >> 63) & 0x1;
final int exponent = ((int)(bits >> 52) & 0x7ff) - 0x3ff;
final long mantissa = bits & 0xfffffffffffffL;
//number is (-1)^sign * 2^(exponent) * 1.mantissa
BigInteger tmpNumerator = BigInteger.valueOf(sign==0 ? 1 : -1);
BigInteger tmpDenominator = BigInteger.ONE;
//use shortcut: 2^x == 1 << x. if x is negative, shift the denominator
if(exponent >= 0)
tmpNumerator = tmpNumerator.multiply(BigInteger.ONE.shiftLeft(exponent));
else
tmpDenominator = tmpDenominator.multiply(BigInteger.ONE.shiftLeft(-exponent));
//1.mantissa == 1 + mantissa/2^52 == (2^52 + mantissa)/2^52
tmpDenominator = tmpDenominator.multiply(BigInteger.valueOf(0x10000000000000L));
tmpNumerator = tmpNumerator.multiply(BigInteger.valueOf(0x10000000000000L + mantissa));
BigInteger gcd = tmpNumerator.gcd(tmpDenominator);
numerator = tmpNumerator.divide(gcd);
denominator = tmpDenominator.divide(gcd);
}
/**
* Constructs a BigFraction from two floating-point numbers.
*
* Warning: round-off error in IEEE floating point numbers can result
* in answers that are unexpected. See BigFraction(double) for more
* information.
*
* NOTE: In many cases, BigFraction(Double.toString(numerator) + "/" + Double.toString(denominator))
* may give a result closer to what the user expects.
*/
public BigFraction(double numerator, double denominator)
{
if(denominator == 0)
throw new ArithmeticException("Divide by zero.");
BigFraction tmp = new BigFraction(numerator).divide(new BigFraction(denominator));
this.numerator = tmp.numerator;
this.denominator = tmp.denominator;
}
/**
* Constructs a new BigFraction from the given BigDecimal object.
*/
public BigFraction(BigDecimal d)
{
this(d.scale() < 0 ? d.unscaledValue().multiply(BigInteger.TEN.pow(-d.scale())) : d.unscaledValue(),
d.scale() < 0 ? BigInteger.ONE : BigInteger.TEN.pow(d.scale()));
}
public BigFraction(BigDecimal numerator, BigDecimal denominator)
{
if(denominator.equals(BigDecimal.ZERO))
throw new ArithmeticException("Divide by zero.");
BigFraction tmp = new BigFraction(numerator).divide(new BigFraction(denominator));
this.numerator = tmp.numerator;
this.denominator = tmp.denominator;
}
/**
* Constructs a BigFraction from a String. Expected format is numerator/denominator,
* but /denominator part is optional. Either numerator or denominator may be a floating-
* point decimal number, which in the same format as a parameter to the
* <code>BigDecimal(String)</code> constructor.
*
* @throws NumberFormatException if the string cannot be properly parsed.
*/
public BigFraction(String s)
{
int slashPos = s.indexOf('/');
if(slashPos < 0)
{
BigFraction res = new BigFraction(new BigDecimal(s));
this.numerator = res.numerator;
this.denominator = res.denominator;
}
else
{
BigDecimal num = new BigDecimal(s.substring(0, slashPos));
BigDecimal den = new BigDecimal(s.substring(slashPos+1, s.length()));
BigFraction res = new BigFraction(num, den);
this.numerator = res.numerator;
this.denominator = res.denominator;
}
}
/**
* Returns this + f.
*/
public BigFraction add(BigFraction f)
{
if(f == null)
throw new IllegalArgumentException("Null argument");
//n1/d1 + n2/d2 = (n1*d2 + d1*n2)/(d1*d2)
return new BigFraction(numerator.multiply(f.denominator).add(denominator.multiply(f.numerator)),
denominator.multiply(f.denominator));
}
/**
* Returns this + b.
*/
public BigFraction add(BigInteger b)
{
if(b == null)
throw new IllegalArgumentException("Null argument");
//n1/d1 + n2 = (n1 + d1*n2)/d1
return new BigFraction(numerator.add(denominator.multiply(b)),
denominator, true);
}
/**
* Returns this + n.
*/
public BigFraction add(long n)
{
return add(BigInteger.valueOf(n));
}
/**
* Returns this - f.
*/
public BigFraction subtract(BigFraction f)
{
if(f == null)
throw new IllegalArgumentException("Null argument");
return new BigFraction(numerator.multiply(f.denominator).subtract(denominator.multiply(f.numerator)),
denominator.multiply(f.denominator));
}
/**
* Returns this - b.
*/
public BigFraction subtract(BigInteger b)
{
if(b == null)
throw new IllegalArgumentException("Null argument");
return new BigFraction(numerator.subtract(denominator.multiply(b)),
denominator, true);
}
/**
* Returns this - n.
*/
public BigFraction subtract(long n)
{
return subtract(BigInteger.valueOf(n));
}
/**
* Returns this * f.
*/
public BigFraction multiply(BigFraction f)
{
if(f == null)
throw new IllegalArgumentException("Null argument");
return new BigFraction(numerator.multiply(f.numerator), denominator.multiply(f.denominator));
}
/**
* Returns this * b.
*/
public BigFraction multiply(BigInteger b)
{
if(b == null)
throw new IllegalArgumentException("Null argument");
return new BigFraction(numerator.multiply(b), denominator);
}
/**
* Returns this * n.
*/
public BigFraction multiply(long n)
{
return multiply(BigInteger.valueOf(n));
}
/**
* Returns this / f.
*/
public BigFraction divide(BigFraction f)
{
if(f == null)
throw new IllegalArgumentException("Null argument");
if(f.numerator.equals(BigInteger.ZERO))
throw new ArithmeticException("Divide by zero");
return new BigFraction(numerator.multiply(f.denominator), denominator.multiply(f.numerator));
}
/**
* Returns this / b.
*/
public BigFraction divide(BigInteger b)
{
if(b == null)
throw new IllegalArgumentException("Null argument");
if(b.equals(BigInteger.ZERO))
throw new ArithmeticException("Divide by zero");
return new BigFraction(numerator, denominator.multiply(b));
}
/**
* Returns this / n.
*/
public BigFraction divide(long n)
{
return divide(BigInteger.valueOf(n));
}
/**
* Returns this^exponent.
*/
public BigFraction pow(int exponent)
{
if(exponent == 0)
return BigFraction.ONE;
else if (exponent == 1)
return this;
else if (exponent < 0)
return new BigFraction(denominator.pow(-exponent), numerator.pow(-exponent), true);
else
return new BigFraction(numerator.pow(exponent), denominator.pow(exponent), true);
}
/**
* Returns 1/this.
*/
public BigFraction reciprocal()
{
if(this.numerator.equals(BigInteger.ZERO))
throw new ArithmeticException("Divide by zero");
return new BigFraction(denominator, numerator, true);
}
/**
* Returns the complement of this fraction, which is equal to 1 - this.
* Useful for probabilities/statistics.
*/
public BigFraction complement()
{
return new BigFraction(denominator.subtract(numerator), denominator, true);
}
/**
* Returns -this.
*/
public BigFraction negate()
{
return new BigFraction(numerator.negate(), denominator, true);
}
/**
* Returns -1, 0, or 1, representing the sign of this fraction.
*/
public int signum()
{
return numerator.signum();
}
/**
* Returns the absolute value of this.
*/
public BigFraction abs()
{
return (signum() < 0 ? negate() : this);
}
/**
* Returns a string representation of this, in the form
* numerator/denominator.
*/
public String toString()
{
return numerator.toString() + "/" + denominator.toString();
}
/**
* Returns if this object is equal to another object.
*/
public boolean equals(Object o)
{
if(!(o instanceof BigFraction))
return false;
BigFraction f = (BigFraction)o;
return numerator.equals(f.numerator) && denominator.equals(f.denominator);
}
/**
* Returns a hash code for this object.
*/
public int hashCode()
{
//using the method generated by Eclipse, but streamlined a bit..
return (31 + numerator.hashCode())*31 + denominator.hashCode();
}
/**
* Returns a negative, zero, or positive number, indicating if this object
* is less than, equal to, or greater than f, respectively.
*/
public int compareTo(BigFraction f)
{
if(f == null)
throw new IllegalArgumentException("Null argument");
//easy case: this and f have different signs
if(signum() != f.signum())
return signum() - f.signum();
//next easy case: this and f have the same denominator
if(denominator.equals(f.denominator))
return numerator.compareTo(f.numerator);
//not an easy case, so first make the denominators equal then compare the numerators
return numerator.multiply(f.denominator).compareTo(denominator.multiply(f.numerator));
}
/**
* Returns the smaller of this and f.
*/
public BigFraction min(BigFraction f)
{
if(f == null)
throw new IllegalArgumentException("Null argument");
return (this.compareTo(f) <= 0 ? this : f);
}
/**
* Returns the maximum of this and f.
*/
public BigFraction max(BigFraction f)
{
if(f == null)
throw new IllegalArgumentException("Null argument");
return (this.compareTo(f) >= 0 ? this : f);
}
/**
* Returns a positive BigFraction, greater than or equal to zero, and less than one.
*/
public static BigFraction random()
{
return new BigFraction(Math.random());
}
public final BigInteger getNumerator() { return numerator; }
public final BigInteger getDenominator() { return denominator; }
//implementation of Number class. may cause overflow.
public byte byteValue() { return (byte) Math.max(Byte.MIN_VALUE, Math.min(Byte.MAX_VALUE, longValue())); }
public short shortValue() { return (short)Math.max(Short.MIN_VALUE, Math.min(Short.MAX_VALUE, longValue())); }
public int intValue() { return (int) Math.max(Integer.MIN_VALUE, Math.min(Integer.MAX_VALUE, longValue())); }
public long longValue() { return Math.round(doubleValue()); }
public float floatValue() { return (float)doubleValue(); }
public double doubleValue() { return toBigDecimal(18).doubleValue(); }
/**
* Returns a BigDecimal representation of this fraction. If possible, the
* returned value will be exactly equal to the fraction. If not, the BigDecimal
* will have a scale large enough to hold the same number of significant figures
* as both numerator and denominator, or the equivalent of a double-precision
* number, whichever is more.
*/
public BigDecimal toBigDecimal()
{
//Implementation note: A fraction can be represented exactly in base-10 iff its
//denominator is of the form 2^a * 5^b, where a and b are nonnegative integers.
//(In other words, if there are no prime factors of the denominator except for
//2 and 5, or if the denominator is 1). So to determine if this denominator is
//of this form, continually divide by 2 to get the number of 2's, and then
//continually divide by 5 to get the number of 5's. Afterward, if the denominator
//is 1 then there are no other prime factors.
//Note: number of 2's is given by the number of trailing 0 bits in the number
int twos = denominator.getLowestSetBit();
BigInteger tmpDen = denominator.shiftRight(twos); // x / 2^n === x >> n
final BigInteger FIVE = BigInteger.valueOf(5);
int fives = 0;
BigInteger[] divMod = null;
//while(tmpDen % 5 == 0) { fives++; tmpDen /= 5; }
while(BigInteger.ZERO.equals((divMod = tmpDen.divideAndRemainder(FIVE))[1]))
{
fives++;
tmpDen = divMod[0];
}
if(BigInteger.ONE.equals(tmpDen))
{
//This fraction will terminate in base 10, so it can be represented exactly as
//a BigDecimal. We would now like to make the fraction of the form
//unscaled / 10^scale. We know that 2^x * 5^x = 10^x, and our denominator is
//in the form 2^twos * 5^fives. So use max(twos, fives) as the scale, and
//multiply the numerator and deminator by the appropriate number of 2's or 5's
//such that the denominator is of the form 2^scale * 5^scale. (Of course, we
//only have to actually multiply the numerator, since all we need for the
//BigDecimal constructor is the scale.
BigInteger unscaled = numerator;
int scale = Math.max(twos, fives);
if(twos < fives)
unscaled = unscaled.shiftLeft(fives - twos); //x * 2^n === x << n
else if (fives < twos)
unscaled = unscaled.multiply(FIVE.pow(twos - fives));
return new BigDecimal(unscaled, scale);
}
//else: this number will repeat infinitely in base-10. So try to figure out
//a good number of significant digits. Start with the number of digits required
//to represent the numerator and denominator in base-10, which is given by
//bitLength / log[2](10). (bitLenth is the number of digits in base-2).
final double LG10 = 3.321928094887362; //Precomputed ln(10)/ln(2), a.k.a. log[2](10)
int precision = Math.max(numerator.bitLength(), denominator.bitLength());
precision = (int)Math.ceil(precision / LG10);
//If the precision is less than 18 digits, use 18 digits so that the number
//will be at least as accurate as a cast to a double. For example, with
//the fraction 1/3, precision will be 1, giving a result of 0.3. This is
//quite a bit different from what a user would expect.
if(precision < 18)
precision = 18;
return toBigDecimal(precision);
}
/**
* Returns a BigDecimal representation of this fraction, with a given precision.
* @param precision the number of significant figures to be used in the result.
*/
public BigDecimal toBigDecimal(int precision)
{
return new BigDecimal(numerator).divide(new BigDecimal(denominator), new MathContext(precision, RoundingMode.HALF_EVEN));
}
//--------------------------------------------------------------------------
// PRIVATE FUNCTIONS
//--------------------------------------------------------------------------
/**
* Private constructor, used when you can be certain that the fraction is already in
* lowest terms. No check is done to reduce numerator/denominator. A check is still
* done to maintain a positive denominator.
*
* @param throwaway unused variable, only here to signal to the compiler that this
* constructor should be used.
*/
private BigFraction(BigInteger numerator, BigInteger denominator, boolean throwaway)
{
if(denominator.signum() < 0)
{
this.numerator = numerator.negate();
this.denominator = denominator.negate();
}
else
{
this.numerator = numerator;
this.denominator = denominator;
}
}
}
以上是 表示Java分数的最佳方法? 的全部内容, 来源链接: utcz.com/qa/401955.html