package net.mightypork.rcalc.numbers; import java.math.BigInteger; import net.mightypork.rcalc.IEvaluableToken; import net.mightypork.rcalc.ParseError; import net.mightypork.rcalc.TokenList; /** * A fractional number with methods for basic arithmetics * * @author Ondrej Hruska */ public class Fraction implements IEvaluableToken { /** Zero fraction (0/1) */ public static final Fraction ZERO = new Fraction(BigInteger.ZERO); /** One fraction (1/1) */ public static final Fraction ONE = new Fraction(BigInteger.ZERO); private BigInteger numerator = BigInteger.ZERO; private BigInteger denominator = BigInteger.ONE; /** * Create fraction from numerator and 1 as denominator * * @param numerator numerator */ public Fraction(long numerator) { this.numerator = BigInteger.valueOf(numerator); this.denominator = BigInteger.ONE; } /** * Create fraction from numerator and 1 as denominator * * @param numerator numerator */ public Fraction(BigInteger numerator) { this.numerator = numerator; this.denominator = BigInteger.ONE; } /** * Create fraction from numerator and denominator * * @param numerator numerator * @param denominator denominator */ public Fraction(long numerator, long denominator) { if (denominator == 0) { throw new ArithmeticException("Division by zero."); } this.numerator = BigInteger.valueOf(numerator); this.denominator = BigInteger.valueOf(denominator); } /** * Create fraction from numerator and denominator * * @param numerator numerator * @param denominator denominator */ public Fraction(BigInteger numerator, BigInteger denominator) { if (denominator.equals(BigInteger.ZERO)) { throw new ArithmeticException("Division by zero."); } this.numerator = numerator; this.denominator = denominator; } /** * Create a fraction as a copy of another * * @param other other fraction to copy */ public Fraction(Fraction other) { this.numerator = other.numerator; this.denominator = other.denominator; } /** * Create a fraction with numerator parsed from a string, and 1 as * denominator. * * @param number numerator as string */ public Fraction(String number) { if (number.matches("[.][0-9]+")) { number = "0" + number; } if (number.matches("-[.][0-9]+")) { number = "-0" + number.substring(1); } if (number.matches("-?[0-9]+[.][0-9]+")) { String[] parts = number.split("[.]"); try { this.numerator = new BigInteger(parts[0] + parts[1]); this.denominator = BigInteger.valueOf(10).pow(parts[1].length()); this.simplify_ip(); } catch (Exception e) { throw new ParseError("Invalid number format."); } } else { this.numerator = new BigInteger(number); this.denominator = BigInteger.ONE; } } /** * Get numerator number * * @return numerator */ public BigInteger getNumerator() { return numerator; } /** * Get denominator number * * @return numerator */ public BigInteger getDenominator() { return numerator; } /** * Add a number * * @param operand addend * @return this + addend */ public Fraction add(Fraction operand) { Fraction a = this.getCopy(); Fraction b = operand.getCopy(); a.numerator = a.numerator.multiply(b.denominator); b.numerator = b.numerator.multiply(a.denominator); a.denominator = a.denominator.multiply(b.denominator); a.numerator = a.numerator.add(b.numerator); return a.simplify(); } /** * Subtract a number * * @param operand subtrahend * @return this - subtrahend */ public Fraction subtract(Fraction operand) { Fraction negated = new Fraction(operand.numerator.negate(), operand.denominator); return this.add(negated); } /** * Multiply by a number * * @param operand multiplier * @return this * multiplier */ public Fraction multiply(Fraction operand) { BigInteger n = numerator.multiply(operand.numerator); BigInteger d = denominator.multiply(operand.denominator); return (new Fraction(n, d)).simplify(); } /** * Divide by a number * * @param operand divisor * @return this / divisor */ public Fraction divide(Fraction operand) { Fraction flipped = new Fraction(operand.denominator, operand.numerator); return this.multiply(flipped); } /** * Modulo with a number * * @param operand divisor * @return this % divisor */ public Fraction modulo(Fraction operand) { if (isFractional() || operand.isFractional()) { throw new ArithmeticException("Modulo is not defined for fractional operands."); } BigInteger i = this.getBigIntegerValue(); BigInteger o = operand.getBigIntegerValue(); return new Fraction(i.mod(o)); } /** * Get an n-th power of the number * * @param operand power * @return this ^ power */ public Fraction power(Fraction operand) { if (operand.isFractional()) { throw new ArithmeticException("Can't calculate fractional power."); } int power = operand.getIntegerValue(); boolean swap = power < 0; power = Math.abs(power); BigInteger n = numerator.pow(power); BigInteger d = denominator.pow(power); Fraction out = (new Fraction(n, d)).simplify(); if (swap) out = out.invert(); return out; } /** * Get a fraction with swapped numerator and denominator. * * @return inverted fraction */ public Fraction invert() { if (numerator.equals(BigInteger.ZERO)) { throw new ArithmeticException("Division by zero."); } return new Fraction(denominator, numerator); } /** * Simplify the fraction * * @return simplified fraction */ public Fraction simplify() { BigInteger gcd = numerator.gcd(denominator); return new Fraction(numerator.divide(gcd), denominator.divide(gcd)); } /** * Simplify the fraction (in place) */ private void simplify_ip() { BigInteger gcd = numerator.gcd(denominator); numerator = numerator.divide(gcd); denominator = denominator.divide(gcd); } /** * Check if the number is fractional * * @return is fractional */ public boolean isFractional() { return !(numerator.mod(denominator)).equals(BigInteger.ZERO); } /** * Get an integer numeric value * * @return value * @throws UnsupportedOperationException for fractional numbers */ public int getIntegerValue() { if (isFractional()) { throw new ArithmeticException("Can't take integer value of a fractional number."); } BigInteger a = numerator.divide(denominator); if (a.longValue() > Integer.MAX_VALUE) { throw new ArithmeticException("Can't convert to integer, number is too big."); } return a.intValue(); } /** * Get a BigInteger numeric value * * @return value * @throws UnsupportedOperationException for fractional numbers */ public BigInteger getBigIntegerValue() { if (this.isFractional()) { throw new ArithmeticException("Can't take BigInteger value of a fractional number."); } return numerator.divide(denominator); } /** * Get a double-precision numeric value * * @return value */ public double getDoubleValue() { double a = numerator.doubleValue(); double b = denominator.doubleValue(); if (Double.isInfinite(a) || Double.isInfinite(b)) { throw new ArithmeticException("Can't convert BigInteger to double, number is too big."); } return a / b; } /** * Get a identical copy of this fraction * * @return copy */ private Fraction getCopy() { return new Fraction(this); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((denominator == null) ? 0 : denominator.hashCode()); result = prime * result + ((numerator == null) ? 0 : numerator.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Fraction other = (Fraction) obj; if (denominator == null || other.denominator == null) return false; if (numerator == null || other.numerator == null) return false; Fraction thisSimplified = this.simplify(); Fraction otherSimplified = other.simplify(); return thisSimplified.numerator.equals(otherSimplified.numerator) && thisSimplified.denominator.equals(otherSimplified.denominator); } /** * Check if the toString() of this fraction equals to the given string * * @param str compared string * @return equals */ public boolean equalsToString(String str) { return this.toString().equals(str); } /** * Get a string representation of this fraction in the format * "numerator/denominator", alternatively "numerator" if denominator is * equal to 1. */ @Override public String toString() { if (!isFractional()) return numerator.divide(denominator).toString(); return numerator + "/" + denominator; } @Override public Fraction evaluate() { return this; } /** * Take a factorial of this number, provided it is not fractional. * * @return the factorial */ public Fraction factorial() { if (this.isFractional()) { throw new ArithmeticException("Can't get factorial of a fractional number."); } BigInteger val = getBigIntegerValue(); if (val.compareTo(BigInteger.ZERO) < 0) { throw new ArithmeticException("Can't get factorial of a negative number."); } BigInteger result = BigInteger.ONE; for (BigInteger cnt = BigInteger.ONE; cnt.compareTo(val) <= 0; cnt = cnt.add(BigInteger.ONE)) { result = result.multiply(cnt); } return new Fraction(result, BigInteger.ONE); } @Override public TokenList wrapInTokenList() { return new TokenList(this); } }