aboutsummaryrefslogtreecommitdiff
path: root/velocity-engine-core/src/main/java/org/apache/velocity/runtime/parser/node/MathUtils.java
diff options
context:
space:
mode:
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.java539
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 &gt; n2, -1 if n1 &lt; 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();
+ }
+ }
+}