/**
* 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
* Byte
, Short
, Integer
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));
}
}