碰巧的是不久前我写了一个BigFraction类,用于解决Euler项目问题。它保留了BigInteger分子和分母,因此它将永远不会溢出。但是,对于许多你永远不会溢出的操作来说,这会有点慢。无论如何,请根据需要使用它。我一直很想以某种方式炫耀它。:)
编辑:该代码的最新,最出色的版本,包括单元测试,现在托管在GitHub上,也可以通过Maven Central获得。我将原始代码留在这里,以便此答案不仅仅是一个链接…
import java.math.*;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); 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); } 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); } 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); } 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; } 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; } 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; } } 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)); } 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); } public BigFraction add(long n) { return add(BigInteger.valueOf(n)); } 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)); } public BigFraction subtract(BigInteger b) { if(b == null) throw new IllegalArgumentException("Null argument"); return new BigFraction(numerator.subtract(denominator.multiply(b)), denominator, true); } public BigFraction subtract(long n) { return subtract(BigInteger.valueOf(n)); } public BigFraction multiply(BigFraction f) { if(f == null) throw new IllegalArgumentException("Null argument"); return new BigFraction(numerator.multiply(f.numerator), denominator.multiply(f.denominator)); } public BigFraction multiply(BigInteger b) { if(b == null) throw new IllegalArgumentException("Null argument"); return new BigFraction(numerator.multiply(b), denominator); } public BigFraction multiply(long n) { return multiply(BigInteger.valueOf(n)); } 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)); } 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)); } public BigFraction divide(long n) { return divide(BigInteger.valueOf(n)); } 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); } public BigFraction reciprocal() { if(this.numerator.equals(BigInteger.ZERO)) throw new ArithmeticException("Divide by zero"); return new BigFraction(denominator, numerator, true); } public BigFraction complement() { return new BigFraction(denominator.subtract(numerator), denominator, true); } public BigFraction negate() { return new BigFraction(numerator.negate(), denominator, true); } public int signum() { return numerator.signum(); } public BigFraction abs() { return (signum() < 0 ? negate() : this); } public String toString() { return numerator.toString() + "/" + denominator.toString(); } public boolean equals(Object o) { if(!(o instanceof BigFraction)) return false; BigFraction f = (BigFraction)o; return numerator.equals(f.numerator) && denominator.equals(f.denominator); } public int hashCode() { //using the method generated by Eclipse, but streamlined a bit.. return (31 + numerator.hashCode())*31 + denominator.hashCode(); } 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)); } public BigFraction min(BigFraction f) { if(f == null) throw new IllegalArgumentException("Null argument"); return (this.compareTo(f) <= 0 ? this : f); } public BigFraction max(BigFraction f) { if(f == null) throw new IllegalArgumentException("Null argument"); return (this.compareTo(f) >= 0 ? this : f); } 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(); } 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); } public BigDecimal toBigDecimal(int precision) { return new BigDecimal(numerator).divide(new BigDecimal(denominator), new MathContext(precision, RoundingMode.HALF_EVEN)); } //-------------------------------------------------------------------------- // PRIVATE FUNCTIONS //-------------------------------------------------------------------------- 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; } }}


