Power.java

package calculator.operations;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;

import calculator.Expression;
import calculator.IllegalConstruction;
import calculator.atoms.*;
import calculator.atoms.visitor.AtomCaster;
import ch.obermuhlner.math.big.BigDecimalMath;

import org.apache.commons.numbers.fraction.Fraction;

public final class Power extends Operation {

	public Power(List<Expression> elist) throws IllegalConstruction {
		super(elist);
		symbol = "**";
		neutral = 1;
	}

	/**
	 * The actual exponentiation of two Reals
	 *
	 * @param r1 the base
	 * @param r2 the exponent
	 * @return the result of the operation
	 */
	@Override
	public Real op(Real r1, Real r2) {

		if (r1.isNan() || r2.isNan())
			return Real.nan();

		BigDecimal base = r1.getValue();
		BigDecimal exponent = r2.getValue();

		// case b^-1 with b=0 or neg
		if (base.compareTo(new BigDecimal(0)) == 0 && exponent.compareTo(new BigDecimal(0)) <= 0) {
			return Real.nan();
		}

		if (r2.isPlusInf()) {
			if (r1.isMinusInf())
				return Real.nan();
			else if (base.compareTo(new BigDecimal(0)) > 0 && base.compareTo(new BigDecimal(1)) < 0)
				return new Real(0);

			else if (base.compareTo(new BigDecimal(0)) == 0 || base.compareTo(new BigDecimal(1)) == 0)
				return Real.nan();

			return Real.plusInf();
		}

		if (r2.isMinusInf()) {
			if (r1.isMinusInf())
				return Real.nan();
			if (base.compareTo(new BigDecimal(0)) > 0 && base.compareTo(new BigDecimal(1)) < 0)
				return Real.plusInf();
			return new Real(0);
		}

		// if negative base, result undefined is irrational exponent or rational
		// exponent with even denominator

		if (base.compareTo(new BigDecimal(0)) < 0) {

			BigInteger[] expFrac = toRational(exponent);
			BigDecimal numerator = new BigDecimal(expFrac[0]);
			BigDecimal denominator = new BigDecimal(expFrac[1]);

			if (expFrac[1].mod(new BigInteger("2")).equals(BigInteger.ONE)) {

				BigDecimal root = BigDecimalMath.pow(base.abs(), BigDecimal.ONE.divide(denominator), Real.context)
						.negate();
				BigDecimal powed = BigDecimalMath.pow(root, numerator, Real.context);
				return new Real(powed);
			}

			// is the expo in not an integer
			if (r2.getValue().stripTrailingZeros().scale() > 0)
				return Real.nan();
		}
		BigDecimal powed = BigDecimalMath.pow(base, exponent, Real.context);
		return new Real(powed);
	}

	public static BigInteger[] toRational(BigDecimal decimal) {
		int scale = decimal.scale();
		if (scale <= 0) {
			return new BigInteger[] { decimal.toBigInteger(), BigInteger.ONE };
		} else {
			BigInteger denominator = BigInteger.TEN.pow(scale);
			BigInteger numerator = decimal.unscaledValue();
			BigInteger d = numerator.gcd(denominator);
			return new BigInteger[] { numerator.divide(d), denominator.divide(d) };
		}
	}

	/**
	 * The actual exponentiation of two Integers.
	 *
	 * @param i1 the base
	 * @param i2 the exponent
	 * @return the result of the operation
	 */
	@Override
	public Real op(IntegerAtom i1, IntegerAtom i2) {
		int base = i1.getValue();
		int exp = i2.getValue();
		if (base == 0 && exp < 0) {
			return Real.nan();
		}
		try {
			if (exp < 0) {
				long denom = Math.powExact(base, -exp);
				return new Real(1.0 / denom);
			} else {
				return new Real(Math.powExact(base, exp));
			}
		} catch (ArithmeticException e) {
			throw new ArithmeticException("Overflow");
		}
	}

	/**
	 * The exponentiation of complex numbers
	 *
	 * @param c1 the base
	 * @param c2 the exponent
	 * @throws ArithmeticException since the exponentiation of complex numbers is
	 *                             impossible
	 */
	@Override
	public Complex op(Complex c1, Complex c2) {
		throw new ArithmeticException("The exponentiation of two complex numbers is impossible");
	}

	/**
	 * The actual exponentiation of two rational numbers
	 *
	 * @param q1 the base
	 * @param q2 the exponent
	 * @return the result of the exponentiation
	 */
	@Override
	public Real op(Rationnal q1, Rationnal q2) {
		AtomCaster aC = new AtomCaster(AtomType.REAL);
		aC.visit(q1);
		Real r1 = (Real) aC.getResult();
		aC.visit(q2);
		Real r2 = (Real) aC.getResult();
		return op(r1, r2);
	}
}