It just so happens that I wrote a BigFraction class not too long ago, for Project Euler problems. It keeps a BigInteger numerator and denominator, so it'll never overflow. But it'll be a tad slow for a lot of operations that you know will never overflow.. anyway, use it if you want it. I've been dying to show this off somehow. :)
Edit: Latest and greatest version of this code, including unit tests is now hosted on GitHub and also available via Maven Central. I'm leaving my original code here so that this answer isn't just a link...
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, represent