diff options
Diffstat (limited to 'velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MathUtils.java')
-rw-r--r-- | velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MathUtils.java | 539 |
1 files changed, 539 insertions, 0 deletions
diff --git a/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MathUtils.java b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MathUtils.java new file mode 100644 index 00000000..52cedccb --- /dev/null +++ b/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MathUtils.java @@ -0,0 +1,539 @@ +package org.apache.velocity.runtime.parser.node; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Utility-class for all arithmetic-operations.<br><br> + * + * All operations (+ - / *) return a Number which type is the type of the bigger argument.<br> + * Example:<br> + * <code>add ( new Integer(10), new Integer(1))</code> will return an <code>Integer</code>-Object with the value 11<br> + * <code>add ( new Long(10), new Integer(1))</code> will return an <code>Long</code>-Object with the value 11<br> + * <code>add ( new Integer(10), new Float(1))</code> will return an <code>Float</code>-Object with the value 11<br><br> + * + * Overflow checking:<br> + * For integral values (byte, short, int) there is an implicit overflow correction (the next "bigger" + * type will be returned). For example, if you call <code>add (new Integer (Integer.MAX_VALUE), 1)</code> a + * <code>Long</code>-object will be returned with the correct value of <code>Integer.MAX_VALUE+1</code>.<br> + * In addition to that the methods <code>multiply</code>,<code>add</code> and <code>substract</code> implement overflow + * checks for <code>long</code>-values. That means that if an overflow occurs while working with long values a BigInteger + * will be returned.<br> + * For all other operations and types (such as Float and Double) there is no overflow checking. + * + * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a> + * @since 1.5 + */ +public abstract class MathUtils +{ + + /** + * A BigDecimal representing the number 0 + */ + protected static final BigDecimal DECIMAL_ZERO = new BigDecimal ( BigInteger.ZERO ); + + /** + * The constants are used to determine in which context we have to calculate. + */ + protected static final int BASE_LONG = 0; + protected static final int BASE_FLOAT = 1; + protected static final int BASE_DOUBLE = 2; + protected static final int BASE_BIGINTEGER = 3; + protected static final int BASE_BIGDECIMAL = 4; + + /** + * The <code>Class</code>-object is key, the maximum-value is the value + */ + private static final Map<Class<? extends Number>, BigDecimal> ints = new HashMap<>(); + static + { + ints.put (Byte.class, BigDecimal.valueOf (Byte.MAX_VALUE)); + ints.put (Short.class, BigDecimal.valueOf (Short.MAX_VALUE)); + ints.put (Integer.class, BigDecimal.valueOf (Integer.MAX_VALUE)); + ints.put (Long.class, BigDecimal.valueOf (Long.MAX_VALUE)); + ints.put (BigInteger.class, BigDecimal.valueOf (-1)); + } + + /** + * The "size" of the number-types - ascending. + */ + private static final List<Class<? extends Number>> typesBySize = new ArrayList<>(); + static + { + typesBySize.add (Byte.class); + typesBySize.add (Short.class); + typesBySize.add (Integer.class); + typesBySize.add (Long.class); + typesBySize.add (Float.class); + typesBySize.add (Double.class); + } + + /** + * Convert the given Number to a BigDecimal + * @param n + * @return The number as BigDecimal + */ + public static BigDecimal toBigDecimal (Number n) + { + + if (n instanceof BigDecimal) + { + return (BigDecimal)n; + } + + if (n instanceof BigInteger) + { + return new BigDecimal ( (BigInteger)n ); + } + + return BigDecimal.valueOf(n.doubleValue()); + + } + + /** + * Convert the given Number to a BigInteger + * @param n + * @return The number as BigInteger + */ + public static BigInteger toBigInteger (Number n) + { + + if (n instanceof BigInteger) + { + return (BigInteger)n; + } + + return BigInteger.valueOf (n.longValue()); + + } + + /** + * Compare the given Number to 0. + * @param n + * @return True if number is 0. + */ + public static boolean isZero (Number n) + { + if (isInteger( n ) ) + { + if (n instanceof BigInteger) + { + return ((BigInteger)n).compareTo (BigInteger.ZERO) == 0; + } + return n.doubleValue() == 0; + } + if (n instanceof Float) + { + return n.floatValue() == 0f; + } + if (n instanceof Double) + { + return n.doubleValue() == 0d; + } + return toBigDecimal( n ).compareTo( DECIMAL_ZERO) == 0; + } + + /** + * Test, whether the given object is an integer value + * (Byte, Short, Integer, Long, BigInteger) + * @param n + * @return True if n is an integer. + */ + public static boolean isInteger (Number n) + { + return ints.containsKey (n.getClass()); + } + + /** + * Wrap the given primitive into the given class if the value is in the + * range of the destination type. If not the next bigger type will be chosen. + * @param value + * @param type + * @return Number object representing the primitive. + */ + public static Number wrapPrimitive (long value, Class<?> type) + { + if (type == Byte.class) + { + if (value > Byte.MAX_VALUE || value < Byte.MIN_VALUE) + { + type = Short.class; + } + else + { + return (byte) value; + } + } + if (type == Short.class) + { + if (value > Short.MAX_VALUE || value < Short.MIN_VALUE) + { + type = Integer.class; + } + else + { + return (short) value; + } + } + if (type == Integer.class) + { + if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) + { + type = Long.class; + } + else + { + return (int) value; + } + } + if (type == Long.class) + { + return value; + } + return BigInteger.valueOf(value); + } + + /** + * Wrap the result in the object of the bigger type. + * + * @param value result of operation (as a long) - used to check size + * @param op1 first operand of binary operation + * @param op2 second operand of binary operation + * @return Number object of appropriate size to fit the value and operators + */ + private static Number wrapPrimitive (long value, Number op1, Number op2) + { + if ( typesBySize.indexOf( op1.getClass()) > typesBySize.indexOf( op2.getClass())) + { + return wrapPrimitive(value, op1.getClass()); + } + return wrapPrimitive(value, op2.getClass()); + } + + /** + * Find the common Number-type to be used in calculations. + * + * @param op1 first operand of binary operation + * @param op2 second operand of binary operation + * @return constant indicating type of Number to use in calculations + */ + private static int findCalculationBase (Number op1, Number op2) + { + + boolean op1Int = isInteger(op1); + boolean op2Int = isInteger(op2); + + if ( (op1 instanceof BigDecimal || op2 instanceof BigDecimal) || + ( (!op1Int || !op2Int) && (op1 instanceof BigInteger || op2 instanceof BigInteger)) ) + { + return BASE_BIGDECIMAL; + } + + if (op1Int && op2Int) { + if (op1 instanceof BigInteger || op2 instanceof BigInteger) + { + return BASE_BIGINTEGER; + } + return BASE_LONG; + } + + if ((op1 instanceof Double) || (op2 instanceof Double)) + { + return BASE_DOUBLE; + } + return BASE_FLOAT; + } + + /** + * Find the Number-type to be used for a single number + * + * @param op operand + * @return constant indicating type of Number to use in calculations + */ + public static int findCalculationBase(Number op) + { + if (isInteger(op)) + { + if (op instanceof BigInteger) + { + return BASE_BIGINTEGER; + } + return BASE_LONG; + } else if (op instanceof BigDecimal) + { + return BASE_BIGDECIMAL; + } else if (op instanceof Double) + { + return BASE_DOUBLE; + } + return BASE_FLOAT; + } + + /** + * Add two numbers and return the correct value / type. + * Overflow detection is done for integer values (byte, short, int, long) only! + * @param op1 + * @param op2 + * @return Addition result. + */ + public static Number add (Number op1, Number op2) + { + + int calcBase = findCalculationBase( op1, op2); + switch (calcBase) + { + case BASE_BIGINTEGER: + return toBigInteger( op1 ).add( toBigInteger( op2 )); + case BASE_LONG: + long l1 = op1.longValue(); + long l2 = op2.longValue(); + long result = l1+l2; + + // Overflow check + if ((result ^ l1) < 0 && (result ^ l2) < 0) + { + return toBigInteger( op1).add( toBigInteger( op2)); + } + return wrapPrimitive( result, op1, op2); + case BASE_FLOAT: + return op1.floatValue() + op2.floatValue(); + case BASE_DOUBLE: + return op1.doubleValue() + op2.doubleValue(); + + // Default is BigDecimal operation + default: + return toBigDecimal( op1 ).add( toBigDecimal( op2 )); + } + } + + /** + * Subtract two numbers and return the correct value / type. + * Overflow detection is done for integer values (byte, short, int, long) only! + * @param op1 + * @param op2 + * @return Subtraction result. + */ + public static Number subtract (Number op1, Number op2) { + + int calcBase = findCalculationBase( op1, op2); + switch (calcBase) { + case BASE_BIGINTEGER: + return toBigInteger( op1 ).subtract( toBigInteger( op2 )); + case BASE_LONG: + long l1 = op1.longValue(); + long l2 = op2.longValue(); + long result = l1-l2; + + // Overflow check + if ((result ^ l1) < 0 && (result ^ ~l2) < 0) { + return toBigInteger( op1).subtract( toBigInteger( op2)); + } + return wrapPrimitive( result, op1, op2); + case BASE_FLOAT: + return op1.floatValue() - op2.floatValue(); + case BASE_DOUBLE: + return op1.doubleValue() - op2.doubleValue(); + + // Default is BigDecimal operation + default: + return toBigDecimal( op1 ).subtract( toBigDecimal( op2 )); + } + } + + /** + * Multiply two numbers and return the correct value / type. + * Overflow detection is done for integer values (byte, short, int, long) only! + * @param op1 + * @param op2 + * @return Multiplication result. + */ + public static Number multiply (Number op1, Number op2) { + + int calcBase = findCalculationBase( op1, op2); + switch (calcBase) { + case BASE_BIGINTEGER: + return toBigInteger( op1 ).multiply( toBigInteger( op2 )); + case BASE_LONG: + long l1 = op1.longValue(); + long l2 = op2.longValue(); + long result = l1*l2; + + // Overflow detection + if ((l2 != 0) && (result / l2 != l1)) { + return toBigInteger( op1).multiply( toBigInteger( op2)); + } + return wrapPrimitive( result, op1, op2); + case BASE_FLOAT: + return op1.floatValue() * op2.floatValue(); + case BASE_DOUBLE: + return op1.doubleValue() * op2.doubleValue(); + + // Default is BigDecimal operation + default: + return toBigDecimal( op1 ).multiply( toBigDecimal( op2 )); + } + } + + /** + * Divide two numbers. The result will be returned as Integer-type if and only if + * both sides of the division operator are Integer-types. Otherwise a Float, Double, + * or BigDecimal will be returned. + * @param op1 + * @param op2 + * @return Division result. + */ + public static Number divide (Number op1, Number op2) { + + int calcBase = findCalculationBase( op1, op2); + switch (calcBase) { + case BASE_BIGINTEGER: + BigInteger b1 = toBigInteger( op1 ); + BigInteger b2 = toBigInteger( op2 ); + return b1.divide( b2); + + case BASE_LONG: + long l1 = op1.longValue(); + long l2 = op2.longValue(); + return wrapPrimitive( l1 / l2, op1, op2); + + case BASE_FLOAT: + return op1.floatValue() / op2.floatValue(); + case BASE_DOUBLE: + return op1.doubleValue() / op2.doubleValue(); + + // Default is BigDecimal operation + default: + return toBigDecimal( op1 ).divide( toBigDecimal( op2 ), BigDecimal.ROUND_HALF_DOWN); + } + } + + /** + * Modulo two numbers. + * @param op1 + * @param op2 + * @return Modulo result. + * + * @throws ArithmeticException If at least one parameter is a BigDecimal + */ + public static Number modulo (Number op1, Number op2) throws ArithmeticException { + + int calcBase = findCalculationBase( op1, op2); + switch (calcBase) { + case BASE_BIGINTEGER: + return toBigInteger( op1 ).mod( toBigInteger( op2 )); + case BASE_LONG: + return wrapPrimitive( op1.longValue() % op2.longValue(), op1, op2); + case BASE_FLOAT: + return op1.floatValue() % op2.floatValue(); + case BASE_DOUBLE: + return op1.doubleValue() % op2.doubleValue(); + + // Default is BigDecimal operation + default: + throw new ArithmeticException( "Cannot calculate the modulo of BigDecimals."); + } + } + + /** + * Compare two numbers. + * @param op1 + * @param op2 + * @return 1 if n1 > n2, -1 if n1 < n2 and 0 if equal. + */ + public static int compare (Number op1, Number op2) { + + int calcBase = findCalculationBase( op1, op2); + switch (calcBase) { + case BASE_BIGINTEGER: + return toBigInteger( op1 ).compareTo( toBigInteger( op2 )); + case BASE_LONG: + long l1 = op1.longValue(); + long l2 = op2.longValue(); + if (l1 < l2) { + return -1; + } + if (l1 > l2) { + return 1; + } + return 0; + case BASE_FLOAT: + float f1 = op1.floatValue(); + float f2 = op2.floatValue(); + if (f1 < f2) { + return -1; + } + if (f1 > f2) { + return 1; + } + return 0; + case BASE_DOUBLE: + double d1 = op1.doubleValue(); + double d2 = op2.doubleValue(); + if (d1 < d2) { + return -1; + } + if (d1 > d2) { + return 1; + } + return 0; + + // Default is BigDecimal operation + default: + return toBigDecimal( op1 ).compareTo( toBigDecimal ( op2 )); + } + } + + /** + * Negate a number + * @param op n + * @return -n (unary negation of n) + */ + public static Number negate(Number op) + { + int calcBase = findCalculationBase( op); + switch (calcBase) { + case BASE_BIGINTEGER: + return toBigInteger(op).negate(); + case BASE_LONG: + long l = op.longValue(); + /* overflow check */ + if (l == Long.MIN_VALUE) + { + return toBigInteger(l).negate(); + } + return wrapPrimitive(-l, op.getClass()); + case BASE_FLOAT: + float f = op.floatValue(); + return -f; + case BASE_DOUBLE: + double d = op.doubleValue(); + return -d; + // Default is BigDecimal operation + default: + return toBigDecimal(op).negate(); + } + } +} |