Data Type Java Tutorial

/**
 * Copyright 2004, 2005, 2006 Odysseus Software GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */ 
import java.math.BigDecimal;
import java.math.BigInteger;
/**
 * Number utilities.
 * 
 * Allows to convert between different java.lang.Number
 * implementations with a minimum of lost information regarding the
 * value of the represented number. Additionally, a few number tests
 * are implemented and exact comparisons of arbitrary numbers may be
 * performed.
 *
 * NOTE: Though some of the methods may give more or less useful results
 * for custom number implementations, they are intended to work only
 * with the predefined types (i.e., Byte, Short, Integer, Long,
 * Float, Double, BigInteger, BigDecimal
).
 *
 * @author Oliver Stuhr
 */
public class NumberUtils {
  /**
   * Answers true iff the given number is an instance of
   * java.math.BigDecimal or java.math.BigInteger.
   *
   * @param number
   * @return boolean
   */
  public static boolean isBig(Number number) {
    return number instanceof BigDecimal || number instanceof BigInteger;
  }
  /**
   * Answers true iff the given number is an instance of
   * ByteShortInteger or Long.
   *
   * @param number
   * @return boolean
   */
  public static boolean isLongCompatible(Number number) {
    return number instanceof Byte || number instanceof Short || number instanceof Integer || number instanceof Long;
  }
  /**
   * Answers true iff the given number is an instance of
   * Float or Double.
   *
   * @param number
   * @return boolean
   */
  public static boolean isDoubleCompatible(Number number) {
    return number instanceof Float || number instanceof Double;
  }
  /**
   * Answers true iff the given number is infinite (i.e., is
   * a Float or Double containing one of the
   * predefined constant values representing positive or negative infinity).
   *
   * @param number
   * @return boolean
   */
  public static boolean isInfinite(Number number) {
    if (number instanceof Double && ((Double)number).isInfinite())
      return true;
    if (number instanceof Float && ((Float)number).isInfinite())
      return true;
    return false;
  }
  /**
   * Answers true iff the given number is 'not a number'
   * (i.e., is a Float or Double containing
   * one of the predefined constant values representing NaN).
   *
   * @param number
   * @return boolean
   */
  public static boolean isNaN(Number number) {
    if (number instanceof Double && ((Double)number).isNaN())
      return true;
    if (number instanceof Float && ((Float)number).isNaN())
      return true;
    return false;
  }
  /**
   * Answers the signum function of the given number
   * (i.e., -1 if it is negative, 0
   * if it is zero and 1 if it is positive).
   *
   * @param number
   * @return int
   * @throws ArithmeticException The given number is null or 'not a number'.
   */
  public static int signum(Number number) throws ArithmeticException {
    if (number == null || isNaN(number))
      throw new ArithmeticException("Argument must not be null or NaN.");
    if (isLongCompatible(number)) {
      long value = number.longValue();
      return value < 0 ? -1 : value == 0 ? 0 : 1;
    } else if (number instanceof BigInteger)
      return ((BigInteger)number).signum();
    else   if (number instanceof BigDecimal)
      return ((BigDecimal)number).signum();
    else {  // => isDoubleCompatible(number) or unknown Number type
      double value = number.doubleValue();
      return value < 0 ? -1 : value == 0 ? 0 : 1;
    }
  }
  /**
   * Converts the given number to a Byte (by using byteValue()).
   *
   * @param number
   * @return java.lang.Byte
   * @throws IllegalArgumentException The given number is 'not a number' or infinite.
   */
  public static Byte toByte(Number number) throws IllegalArgumentException {
    if (number == null || number instanceof Byte)
      return (Byte)number;
    if (isNaN(number) || isInfinite(number))
      throw new IllegalArgumentException("Argument must not be NaN or infinite.");
    return new Byte(number.byteValue());
  }
  /**
   * Converts the given number to a Short (by using shortValue()).
   *
   * @param number
   * @return java.lang.Short
   * @throws IllegalArgumentException The given number is 'not a number' or infinite.
   */
  public static Short toShort(Number number) throws IllegalArgumentException {
    if (number == null || number instanceof Short)
      return (Short)number;
    if (isNaN(number) || isInfinite(number))
      throw new IllegalArgumentException("Argument must not be NaN or infinite.");
    return new Short(number.shortValue());
  }
  /**
   * Converts the given number to a Integer (by using intValue()).
   *
   * @param number
   * @return java.lang.Integer
   * @throws IllegalArgumentException The given number is 'not a number' or infinite.
   */
  public static Integer toInteger(Number number) throws IllegalArgumentException {
    if (number == null || number instanceof Integer)
      return (Integer)number;
    if (isNaN(number) || isInfinite(number))
      throw new IllegalArgumentException("Argument must not be NaN or infinite.");
    return new Integer(number.intValue());
  }
  /**
   * Converts the given number to a Long (by using longValue()).
   *
   * @param number
   * @return java.lang.Long
   * @throws IllegalArgumentException The given number is 'not a number' or infinite.
   */
  public static Long toLong(Number number) throws IllegalArgumentException {
    if (number == null || number instanceof Long)
      return (Long)number;
    if (isNaN(number) || isInfinite(number))
      throw new IllegalArgumentException("Argument must not be NaN or infinite.");
    return new Long(number.longValue());
  }
  /**
   * Converts the given number to a Float (by using floatValue()).
   *
   * @param number
   * @return java.lang.Float
   */
  public static Float toFloat(Number number) {
    return number == null || number instanceof Float ? (Float)number : new Float(number.floatValue());
  }
  /**
   * Converts the given number to a Double (by using doubleValue()).
   *
   * @param number
   * @return java.lang.Double
   */
  public static Double toDouble(Number number) {
    return number == null || number instanceof Double ? (Double)number : new Double(number.doubleValue());
  }
  /**
   * Converts the given number to a java.math.BigInteger.
   *
   * @param number
   * @return java.math.BigInteger
   * @throws IllegalArgumentException The given number is 'not a number' or infinite.
   */
  public static BigInteger toBigInteger(Number number) throws IllegalArgumentException {
    if (number == null || number instanceof BigInteger)
      return (BigInteger)number;
    if (number instanceof BigDecimal)
      return ((BigDecimal)number).toBigInteger();
    if (isDoubleCompatible(number)) {
      if (isNaN(number) || isInfinite(number))
        throw new IllegalArgumentException("Argument must not be NaN or infinite.");
      return new BigDecimal(number.toString()).toBigInteger();
    } // => isLongCompatible(number) or unknown Number type
    return BigInteger.valueOf(number.longValue());
  }
  /**
   * Converts the given number to a java.math.BigDecimal.
   *
   * @param number
   * @return java.math.BigDecimal
   * @throws IllegalArgumentException The given number is 'not a number' or infinite.
   */
  public static BigDecimal toBigDecimal(Number number) throws IllegalArgumentException {
    if (number == null || number instanceof BigDecimal)
      return (BigDecimal)number;
    if (number instanceof BigInteger)
      return new BigDecimal((BigInteger)number);
    if (isDoubleCompatible(number)) {
      if (isNaN(number) || isInfinite(number))
        throw new IllegalArgumentException("Argument must not be NaN or infinite.");
      return new BigDecimal(number.toString());
    }
    if (isLongCompatible(number))
      return BigDecimal.valueOf(number.longValue());
    // => unknown Number type
    return new BigDecimal(String.valueOf(number.doubleValue()));
  }
  /**
   * Compares the first number to the second one numerically and 
   * returns an integer depending on the comparison result:
   * a negative value if the first number is the smaller one,
   * a zero value if they are equal, and
   * a positive value if the first number is the larger one.
   *
   * The main strategy goes like follows:
   * 1. If one of the arguments is null or 'not a number',
   *    throw an exception.
   * 2. If both values are 'long compatible', compare their longValue()
   *    using the usual comparison operators for primitive types (<, ==, >).
   * 3. If both values are 'double compatible', compare their doubleValue()
   *    using the usual comparison operators for primitive types (<, ==, >).
   * 4. If one of the values is infinite (and the other is finite),
   *    determine the result depending on the sign of the infinite value.
   * 5. Otherwise convert both values to java.math.BigDecimal and
   *    return the result of the BigDecimal.compareTo(BigDecimal) method.
   *
   * As a consequence, the method is not suitable to implement a
   * java.util.Comparator for numbers. To achieve this,
   * one had to accept 'not a number' arguments and place them somewhere
   * in the row of numbers (probably at the upper end, i.e. larger than
   * positive infinity, as Double.compare(double, double)
   * does it).
   * So the behavior of this method is like that of the comparison
   * operator for primitive types and not like that of the related
   * compareTo(...) methods. Besides the handling of
   * 'not a number' values this makes a difference, when comparing
   * the float or double values -0.0 and 0.0:
   * again, like the operators, we consider them as equal (whereas
   * according to Double.compareTo(...) -0.0
   * is less than 0.0).
   *
   * @param first
   * @param second
   * @return int
   * @throws ArithmeticException One or both of the given numbers is null or 'not a number'.
   */
  public static int compare(Number first, Number second) throws ArithmeticException {
    if (first == null || second == null || isNaN(first) || isNaN(second))
      throw new ArithmeticException("Arguments must not be null or NaN.");
    int result = -2;
    if (isLongCompatible(first) && isLongCompatible(second)) {
      long v1 = first.longValue(), v2 = second.longValue();
      result = v1 < v2 ? -1 : v1 == v2 ? 0 : v1 > v2 ? 1 : 2;
    } else if (isDoubleCompatible(first) && isDoubleCompatible(second)) {
      double v1 = first.doubleValue(), v2 = second.doubleValue();
      result = v1 < v2 ? -1 : v1 == v2 ? 0 : v1 > v2 ? 1 : 2;
    }
    if (result == 2)    // should not happen
      throw new ArithmeticException("Arguments " + first + " and " + second + " are not comparable.");
    if (result > -2)
      return result;
    if (isInfinite(first))    // => second is finite
      return first.doubleValue() == Double.NEGATIVE_INFINITY ? -1 : 1;
    if (isInfinite(second))   // => first is finite
      return second.doubleValue() == Double.POSITIVE_INFINITY ? -1 : 1;
    return toBigDecimal(first).compareTo(toBigDecimal(second));
  }
}