package ModSQL;
import java.math.*;
 
/* $Id: NumberMath.java,v 1.3 2003/05/29 05:48:45 cvs Exp $
 *
 * Copyright (c) 2003 Chris Studholme <chris.studholme@utoronto.ca>
 *
 * May be copied or modified under the terms of the GNU General Public
 * License.  See COPYING for more information.
 */

/**
 * A class very much like java.lang.Math, but for doing math on Number objects.
 * It would be nice if this class could extend java.lang.Math (and be called
 * Math itself), but since java.lang.Math is final, we can't do that.
 *
 * @author chris.studholme@utoronto.ca
 */
public class NumberMath {

  /**
   * Convert a Number object to a BigDecimal object.
   *
   * @param n number to convert
   * @return number as a BigDecimal object
   */
  public static BigDecimal toBigDecimal(Number n) {
    if (n instanceof BigDecimal)
      return (BigDecimal)n;
    if (n instanceof BigInteger)
      return new BigDecimal((BigInteger)n);
    if (n instanceof Long)
      return BigDecimal.valueOf(n.longValue());
    return new BigDecimal(n.doubleValue());
  }

  /**
   * Convert a Number object to a BigInteger object.
   *
   * @param n number to convert
   * @return number as a BigInteger object
   */
  public static BigInteger toBigInteger(Number n) {
    if (n instanceof BigInteger)
      return (BigInteger)n;
    if (n instanceof BigDecimal)
      return ((BigDecimal)n).toBigInteger();
    if (n instanceof Double || n instanceof Float)
      return new BigDecimal(n.doubleValue()).toBigInteger();
    return BigInteger.valueOf(n.longValue());
  }

  /**
   * Negate a Number object.
   *
   * @param n number to negate
   * @return negated number
   * @throws IllegalArgumentException if n is an unknown subtype of Number
   */
  public static Number negate(Number n) {
    if (n instanceof Integer)
      return new Integer(-n.intValue());
    if (n instanceof Double)
      return new Double(-n.doubleValue());
    if (n instanceof Long)
      return new Long(-n.longValue());
    if (n instanceof Float)
      return new Float(-n.floatValue());
    if (n instanceof Short)
      return new Short((short)-n.shortValue());
    if (n instanceof Byte)
      return new Byte((byte)-n.byteValue());
    if (n instanceof BigDecimal)
      return ((BigDecimal)n).negate();
    if (n instanceof BigInteger)
      return ((BigInteger)n).negate();
    throw new IllegalArgumentException("unknown Number object");
  }

  /**
   * Add two Number objects to produce an appropriate Number object.
   *
   * @param a first number
   * @param b second number
   * @return sum of a and b
   * @throws IllegalArgumentException if n is an unknown subtype of Number
   */
  public static Number add(Number a, Number b) {
    if (a instanceof BigDecimal)
      return ((BigDecimal)a).add(toBigDecimal(b));
    if (b instanceof BigDecimal)
      return ((BigDecimal)b).add(toBigDecimal(a));
    if (a instanceof BigInteger) {
      if (b instanceof Double || b instanceof Float)
	return 
	  new BigDecimal((BigInteger)a).add(new BigDecimal(b.doubleValue()));
      return ((BigInteger)a).add(toBigInteger(b));
    }
    if (b instanceof BigInteger) {
      if (a instanceof Double || a instanceof Float)
	return 
	  new BigDecimal((BigInteger)b).add(new BigDecimal(a.doubleValue()));
      return ((BigInteger)b).add(toBigInteger(a));
    }
    if (a instanceof Double || b instanceof Double)
      return new Double(a.doubleValue()+b.doubleValue());
    if (a instanceof Long || b instanceof Long) {
      if (a instanceof Float || b instanceof Float)
	return new Double(a.doubleValue()+b.doubleValue());
      return new Long(a.longValue()+b.longValue());
    }
    if (a instanceof Float || b instanceof Float)
      return new Float(a.floatValue()+b.floatValue());
    if (a instanceof Integer || b instanceof Integer)
      return new Integer(a.intValue()+b.intValue());
    if (a instanceof Short || b instanceof Short)
      return new Short((short)(a.shortValue()+b.shortValue()));
    if (a instanceof Byte || b instanceof Byte)
      return new Byte((byte)(a.byteValue()+b.byteValue()));
    throw new IllegalArgumentException("unknown Number object");
  }

  /**
   * Subtract two Number objects to produce an appropriate Number object.
   */
  public static Number subtract(Number a, Number b) {
    return add(a,negate(b));
  }

  /**
   * Multiply two Number objects to produce an appropriate Number object.
   *
   * @param a first number
   * @param b second number
   * @return product of a and b
   * @throws IllegalArgumentException if n is an unknown subtype of Number
   */
  public static Number multiply(Number a, Number b) {
    if (a instanceof BigDecimal)
      return ((BigDecimal)a).multiply(toBigDecimal(b));
    if (b instanceof BigDecimal)
      return ((BigDecimal)b).multiply(toBigDecimal(a));
    if (a instanceof BigInteger) {
      if (b instanceof Double || b instanceof Float)
	return new BigDecimal((BigInteger)a).
	  multiply(new BigDecimal(b.doubleValue()));
      return ((BigInteger)a).multiply(toBigInteger(b));
    }
    if (b instanceof BigInteger) {
      if (a instanceof Double || a instanceof Float)
	return new BigDecimal((BigInteger)b).
	  multiply(new BigDecimal(a.doubleValue()));
      return ((BigInteger)b).multiply(toBigInteger(a));
    }
    if (a instanceof Double || b instanceof Double)
      return new Double(a.doubleValue()*b.doubleValue());
    if (a instanceof Long || b instanceof Long) {
      if (a instanceof Float || b instanceof Float)
	return new Double(a.doubleValue()*b.doubleValue());
      return new Long(a.longValue()*b.longValue());
    }
    if (a instanceof Float || b instanceof Float)
      return new Float(a.floatValue()*b.floatValue());
    if (a instanceof Integer || b instanceof Integer)
      return new Integer(a.intValue()*b.intValue());
    if (a instanceof Short || b instanceof Short)
      return new Short((short)(a.shortValue()*b.shortValue()));
    if (a instanceof Byte || b instanceof Byte)
      return new Byte((byte)(a.byteValue()*b.byteValue()));
    throw new IllegalArgumentException("unknown Number object");
  }

  /**
   * Divide two Number objects to produce an appropriate Number object.
   *
   * @param a first number
   * @param b second number
   * @return a/b
   * @throws IllegalArgumentException if n is an unknown subtype of Number
   */
  public static Number divide(Number a, Number b) {
    if (a instanceof BigDecimal)
      return ((BigDecimal)a).divide(toBigDecimal(b),BigDecimal.ROUND_HALF_UP);
    if (b instanceof BigDecimal)
      return toBigDecimal(a).divide((BigDecimal)b,BigDecimal.ROUND_HALF_UP);
    if (a instanceof BigInteger) {
      if (b instanceof Double || b instanceof Float)
	return new BigDecimal((BigInteger)a).
	  divide(new BigDecimal(b.doubleValue()),BigDecimal.ROUND_HALF_UP);
      return ((BigInteger)a).divide(toBigInteger(b));
    }
    if (b instanceof BigInteger) {
      if (a instanceof Double || a instanceof Float)
	return new BigDecimal(a.doubleValue()).
	  divide(new BigDecimal((BigInteger)b),BigDecimal.ROUND_HALF_UP);
      return toBigInteger(a).divide((BigInteger)b);
    }
    if (a instanceof Double || b instanceof Double)
      return new Double(a.doubleValue()/b.doubleValue());
    if (a instanceof Long || b instanceof Long) {
      if (a instanceof Float || b instanceof Float)
	return new Double(a.doubleValue()/b.doubleValue());
      return new Long(a.longValue()/b.longValue());
    }
    if (a instanceof Float || b instanceof Float)
      return new Float(a.floatValue()/b.floatValue());
    if (a instanceof Integer || b instanceof Integer)
      return new Integer(a.intValue()/b.intValue());
    if (a instanceof Short || b instanceof Short)
      return new Short((short)(a.shortValue()/b.shortValue()));
    if (a instanceof Byte || b instanceof Byte)
      return new Byte((byte)(a.byteValue()/b.byteValue()));
    throw new IllegalArgumentException("unknown Number object");
  }

  /**
   * Returns the absolute value of a Number object.
   *
   * @param n the argument whose absolute value is to be determined
   * @return the absolute value of the argument
   * @throws IllegalArgumentException if n is an unknown subtype of Number
   */
  public static Number abs(Number n) {
    if (n instanceof Double)
      return new Double(Math.abs(n.doubleValue()));
    if (n instanceof Float)
      return new Float(Math.abs(n.floatValue()));
    if (n instanceof Long)
      return new Long(Math.abs(n.longValue()));
    if (n instanceof Integer)
      return new Integer(Math.abs(n.intValue()));
    if (n instanceof Short)
      return new Short((short)Math.abs(n.intValue()));
    if (n instanceof Byte)
      return new Byte((byte)Math.abs(n.intValue()));
    if (n instanceof BigInteger)
      return ((BigInteger)n).abs();
    if (n instanceof BigDecimal)
      return ((BigDecimal)n).abs();
    throw new IllegalArgumentException("unknown Number object");
  }

  /**
   * Returns the smallest (closest to negative infinity) Number that is not
   * less than the argument and is equal to a mathematical integer.  If the
   * number if of integer type, it is simply returned.  Unknown subtypes of
   * number are assumed to be integers.
   *
   * @param n a number
   * @return the smallest (closest to negative infinity) floating-point value
   * that is not less than the argument and is equal to a mathematical integer
   */
  public static Number ceil(Number n) {
    if (n instanceof Double)
      return new Double(Math.ceil(n.doubleValue()));
    if (n instanceof Float)
      return new Float(Math.ceil(n.doubleValue()));
    if (n instanceof BigDecimal)
      throw new UnsupportedOperationException("cannot handle BigDecimal object");
    // object is assumed to be an integer
    return n;
  }

  /**
   * Returns the largest (closest to positive infinity) Number that is not
   * greater than the argument and is equal to a mathematical integer.  If the
   * number if of integer type, it is simply returned.  Unknown subtypes of
   * number are assumed to be integers.
   *
   * @param n a number
   * @return the smallest (closest to negative infinity) floating-point value
   * that is not less than the argument and is equal to a mathematical integer
   */
  public static Number floor(Number n) {
    if (n instanceof Double)
      return new Double(Math.floor(n.doubleValue()));
    if (n instanceof Float)
      return new Float(Math.floor(n.doubleValue()));
    if (n instanceof BigDecimal)
      throw new UnsupportedOperationException("cannot handle BigDecimal object");
    // object is assumed to be an integer
    return n;
  }

  /**
   * Determine the sign of a number.  This method uses the doubleValue()
   * method of Number and then compares the result with 0 to determine the
   * return value.
   *
   * @param n a number
   * @return -1 is the number is negative, +1 if it is positive, 0 otherwise
   */
  public static int sign(Number n) {
    double d = n.doubleValue();
    return d<0 ? -1 : d>0 ? 1 : 0;
  }

  /**
   * Returns the correctly rounded positive square root of a Number object.
   * This method simply converts the Number to a double using doubleValue(),
   * and then uses java.lang.Math.sqrt() to compute the square root.
   */
  public static double sqrt(Number n) {
    return Math.sqrt(n.doubleValue());
  }

  /**
   * Compute the least non-negative residue of n mod d.  Both n and d must
   * be integer types.
   *
   * @param n numerator
   * @param d modulus
   * @return n%d as a Number object with the same type as d
   * @throws IllegalArgumentException if n or d is an unknown subtype of Number
   */
  public static Number mod(Number n, Number d) {
    if (n instanceof BigInteger) {
      if (d instanceof BigInteger)
	return ((BigInteger)n).mod((BigInteger)d);
      Number result = ((BigInteger)n).mod(BigInteger.valueOf(d.longValue()));
      if (d instanceof Long)
	return new Long(result.longValue());
      if (d instanceof Integer)
	return new Integer(result.intValue());
      if (d instanceof Short)
	return new Short(result.shortValue());
      if (d instanceof Byte)
	return new Byte(result.byteValue());
      throw new IllegalArgumentException("unknown Number object");
    }
    // assume n is an integer of type Long or smaller
    if (d instanceof Long)
      return new Long(n.longValue()%d.longValue());
    if (d instanceof Integer)
      return new Integer((int)(n.longValue()%d.intValue()));
    if (d instanceof Short)
      return new Short((short)(n.longValue()%d.intValue()));
    if (d instanceof Byte)
      return new Byte((byte)(n.longValue()%d.intValue()));
    throw new IllegalArgumentException("unknown Number object");
  }

};

