001: /*
002: * Copyright 2004, 2005, 2006 Odysseus Software GmbH
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package de.odysseus.calyxo.base.util;
017:
018: import java.math.BigDecimal;
019: import java.math.BigInteger;
020:
021: /**
022: * Number utilities.
023: *
024: * Allows to convert between different <code>java.lang.Number</code>
025: * implementations with a minimum of lost information regarding the
026: * value of the represented number. Additionally, a few number tests
027: * are implemented and exact comparisons of arbitrary numbers may be
028: * performed.
029: *
030: * NOTE: Though some of the methods may give more or less useful results
031: * for custom number implementations, they are intended to work only
032: * with the predefined types (i.e., <code>Byte, Short, Integer, Long,
033: * Float, Double, BigInteger, BigDecimal</code>).
034: *
035: * @author Oliver Stuhr
036: */
037: public class NumberUtils {
038: /**
039: * Answers <code>true</code> iff the given number is an instance of
040: * <code>java.math.BigDecimal</code> or <code>java.math.BigInteger</code>.
041: *
042: * @param number
043: * @return boolean
044: */
045: public static boolean isBig(Number number) {
046: return number instanceof BigDecimal
047: || number instanceof BigInteger;
048: }
049:
050: /**
051: * Answers <code>true</code> iff the given number is an instance of
052: * <code>Byte</code>, <code>Short</code>, <code>Integer</code> or <code>Long</code>.
053: *
054: * @param number
055: * @return boolean
056: */
057: public static boolean isLongCompatible(Number number) {
058: return number instanceof Byte || number instanceof Short
059: || number instanceof Integer || number instanceof Long;
060: }
061:
062: /**
063: * Answers <code>true</code> iff the given number is an instance of
064: * <code>Float</code> or <code>Double</code>.
065: *
066: * @param number
067: * @return boolean
068: */
069: public static boolean isDoubleCompatible(Number number) {
070: return number instanceof Float || number instanceof Double;
071: }
072:
073: /**
074: * Answers <code>true</code> iff the given number is infinite (i.e., is
075: * a <code>Float</code> or <code>Double</code> containing one of the
076: * predefined constant values representing positive or negative infinity).
077: *
078: * @param number
079: * @return boolean
080: */
081: public static boolean isInfinite(Number number) {
082: if (number instanceof Double && ((Double) number).isInfinite())
083: return true;
084: if (number instanceof Float && ((Float) number).isInfinite())
085: return true;
086: return false;
087: }
088:
089: /**
090: * Answers <code>true</code> iff the given number is 'not a number'
091: * (i.e., is a <code>Float</code> or <code>Double</code> containing
092: * one of the predefined constant values representing <code>NaN</code>).
093: *
094: * @param number
095: * @return boolean
096: */
097: public static boolean isNaN(Number number) {
098: if (number instanceof Double && ((Double) number).isNaN())
099: return true;
100: if (number instanceof Float && ((Float) number).isNaN())
101: return true;
102: return false;
103: }
104:
105: /**
106: * Answers the signum function of the given number
107: * (i.e., <code>-1</code> if it is negative, <code>0</code>
108: * if it is zero and <code>1</code> if it is positive).
109: *
110: * @param number
111: * @return int
112: * @throws ArithmeticException The given number is <code>null</code> or 'not a number'.
113: */
114: public static int signum(Number number) throws ArithmeticException {
115: if (number == null || isNaN(number))
116: throw new ArithmeticException(
117: "Argument must not be null or NaN.");
118:
119: if (isLongCompatible(number)) {
120: long value = number.longValue();
121: return value < 0 ? -1 : value == 0 ? 0 : 1;
122: } else if (number instanceof BigInteger)
123: return ((BigInteger) number).signum();
124: else if (number instanceof BigDecimal)
125: return ((BigDecimal) number).signum();
126: else { // => isDoubleCompatible(number) or unknown Number type
127: double value = number.doubleValue();
128: return value < 0 ? -1 : value == 0 ? 0 : 1;
129: }
130: }
131:
132: /**
133: * Converts the given number to a <code>Byte</code> (by using <code>byteValue()</code>).
134: *
135: * @param number
136: * @return java.lang.Byte
137: * @throws IllegalArgumentException The given number is 'not a number' or infinite.
138: */
139: public static Byte toByte(Number number)
140: throws IllegalArgumentException {
141: if (number == null || number instanceof Byte)
142: return (Byte) number;
143: if (isNaN(number) || isInfinite(number))
144: throw new IllegalArgumentException(
145: "Argument must not be NaN or infinite.");
146: return new Byte(number.byteValue());
147: }
148:
149: /**
150: * Converts the given number to a <code>Short</code> (by using <code>shortValue()</code>).
151: *
152: * @param number
153: * @return java.lang.Short
154: * @throws IllegalArgumentException The given number is 'not a number' or infinite.
155: */
156: public static Short toShort(Number number)
157: throws IllegalArgumentException {
158: if (number == null || number instanceof Short)
159: return (Short) number;
160: if (isNaN(number) || isInfinite(number))
161: throw new IllegalArgumentException(
162: "Argument must not be NaN or infinite.");
163: return new Short(number.shortValue());
164: }
165:
166: /**
167: * Converts the given number to a <code>Integer</code> (by using <code>intValue()</code>).
168: *
169: * @param number
170: * @return java.lang.Integer
171: * @throws IllegalArgumentException The given number is 'not a number' or infinite.
172: */
173: public static Integer toInteger(Number number)
174: throws IllegalArgumentException {
175: if (number == null || number instanceof Integer)
176: return (Integer) number;
177: if (isNaN(number) || isInfinite(number))
178: throw new IllegalArgumentException(
179: "Argument must not be NaN or infinite.");
180: return new Integer(number.intValue());
181: }
182:
183: /**
184: * Converts the given number to a <code>Long</code> (by using <code>longValue()</code>).
185: *
186: * @param number
187: * @return java.lang.Long
188: * @throws IllegalArgumentException The given number is 'not a number' or infinite.
189: */
190: public static Long toLong(Number number)
191: throws IllegalArgumentException {
192: if (number == null || number instanceof Long)
193: return (Long) number;
194: if (isNaN(number) || isInfinite(number))
195: throw new IllegalArgumentException(
196: "Argument must not be NaN or infinite.");
197: return new Long(number.longValue());
198: }
199:
200: /**
201: * Converts the given number to a <code>Float</code> (by using <code>floatValue()</code>).
202: *
203: * @param number
204: * @return java.lang.Float
205: */
206: public static Float toFloat(Number number) {
207: return number == null || number instanceof Float ? (Float) number
208: : new Float(number.floatValue());
209: }
210:
211: /**
212: * Converts the given number to a <code>Double</code> (by using <code>doubleValue()</code>).
213: *
214: * @param number
215: * @return java.lang.Double
216: */
217: public static Double toDouble(Number number) {
218: return number == null || number instanceof Double ? (Double) number
219: : new Double(number.doubleValue());
220: }
221:
222: /**
223: * Converts the given number to a <code>java.math.BigInteger</code>.
224: *
225: * @param number
226: * @return java.math.BigInteger
227: * @throws IllegalArgumentException The given number is 'not a number' or infinite.
228: */
229: public static BigInteger toBigInteger(Number number)
230: throws IllegalArgumentException {
231: if (number == null || number instanceof BigInteger)
232: return (BigInteger) number;
233: if (number instanceof BigDecimal)
234: return ((BigDecimal) number).toBigInteger();
235: if (isDoubleCompatible(number)) {
236: if (isNaN(number) || isInfinite(number))
237: throw new IllegalArgumentException(
238: "Argument must not be NaN or infinite.");
239: return new BigDecimal(number.toString()).toBigInteger();
240: } // => isLongCompatible(number) or unknown Number type
241: return BigInteger.valueOf(number.longValue());
242: }
243:
244: /**
245: * Converts the given number to a <code>java.math.BigDecimal</code>.
246: *
247: * @param number
248: * @return java.math.BigDecimal
249: * @throws IllegalArgumentException The given number is 'not a number' or infinite.
250: */
251: public static BigDecimal toBigDecimal(Number number)
252: throws IllegalArgumentException {
253: if (number == null || number instanceof BigDecimal)
254: return (BigDecimal) number;
255: if (number instanceof BigInteger)
256: return new BigDecimal((BigInteger) number);
257: if (isDoubleCompatible(number)) {
258: if (isNaN(number) || isInfinite(number))
259: throw new IllegalArgumentException(
260: "Argument must not be NaN or infinite.");
261: return new BigDecimal(number.toString());
262: }
263: if (isLongCompatible(number))
264: return BigDecimal.valueOf(number.longValue());
265: // => unknown Number type
266: return new BigDecimal(String.valueOf(number.doubleValue()));
267: }
268:
269: /**
270: * Compares the first number to the second one numerically and
271: * returns an integer depending on the comparison result:
272: * a negative value if the first number is the smaller one,
273: * a zero value if they are equal, and
274: * a positive value if the first number is the larger one.
275: *
276: * The main strategy goes like follows:
277: * 1. If one of the arguments is <code>null</code> or 'not a number',
278: * throw an exception.
279: * 2. If both values are 'long compatible', compare their <code>longValue()</code>
280: * using the usual comparison operators for primitive types (<, ==, >).
281: * 3. If both values are 'double compatible', compare their <code>doubleValue()</code>
282: * using the usual comparison operators for primitive types (<, ==, >).
283: * 4. If one of the values is infinite (and the other is finite),
284: * determine the result depending on the sign of the infinite value.
285: * 5. Otherwise convert both values to <code>java.math.BigDecimal</code> and
286: * return the result of the <code>BigDecimal.compareTo(BigDecimal)</code> method.
287: *
288: * As a consequence, the method is not suitable to implement a
289: * <code>java.util.Comparator</code> for numbers. To achieve this,
290: * one had to accept 'not a number' arguments and place them somewhere
291: * in the row of numbers (probably at the upper end, i.e. larger than
292: * positive infinity, as <code>Double.compare(double, double)</code>
293: * does it).
294: * So the behavior of this method is like that of the comparison
295: * operator for primitive types and not like that of the related
296: * <code>compareTo(...)</code> methods. Besides the handling of
297: * 'not a number' values this makes a difference, when comparing
298: * the float or double values <code>-0.0</code> and <code>0.0</code>:
299: * again, like the operators, we consider them as equal (whereas
300: * according to <code>Double.compareTo(...)</code> <code>-0.0</code>
301: * is less than <code>0.0</code>).
302: *
303: * @param first
304: * @param second
305: * @return int
306: * @throws ArithmeticException One or both of the given numbers is <code>null</code> or 'not a number'.
307: */
308: public static int compare(Number first, Number second)
309: throws ArithmeticException {
310: if (first == null || second == null || isNaN(first)
311: || isNaN(second))
312: throw new ArithmeticException(
313: "Arguments must not be null or NaN.");
314:
315: int result = -2;
316:
317: if (isLongCompatible(first) && isLongCompatible(second)) {
318: long v1 = first.longValue(), v2 = second.longValue();
319: result = v1 < v2 ? -1 : v1 == v2 ? 0 : v1 > v2 ? 1 : 2;
320: } else if (isDoubleCompatible(first)
321: && isDoubleCompatible(second)) {
322: double v1 = first.doubleValue(), v2 = second.doubleValue();
323: result = v1 < v2 ? -1 : v1 == v2 ? 0 : v1 > v2 ? 1 : 2;
324: }
325:
326: if (result == 2) // should not happen
327: throw new ArithmeticException("Arguments " + first
328: + " and " + second + " are not comparable.");
329: if (result > -2)
330: return result;
331:
332: if (isInfinite(first)) // => second is finite
333: return first.doubleValue() == Double.NEGATIVE_INFINITY ? -1
334: : 1;
335: if (isInfinite(second)) // => first is finite
336: return second.doubleValue() == Double.POSITIVE_INFINITY ? -1
337: : 1;
338:
339: return toBigDecimal(first).compareTo(toBigDecimal(second));
340: }
341: }
|