我正在尝试使用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; } } }