freemarker.core.ArithmeticEngine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of freemarker Show documentation
Show all versions of freemarker Show documentation
FreeMarker is a "template engine"; a generic tool to generate text output based on templates.
/*
* 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.
*/
package freemarker.core;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import freemarker.template.TemplateException;
import freemarker.template.utility.NumberUtil;
import freemarker.template.utility.OptimizerUtil;
import freemarker.template.utility.StringUtil;
/**
* Used for implementing the arithmetic operations and number comparisons in the template language. The concrete
* implementation is plugged into the configuration with the {@code arithmetical_engine} setting.
* (See {@link Configurable#setArithmeticEngine(ArithmeticEngine)}.)
*/
public abstract class ArithmeticEngine {
/**
* Arithmetic engine that converts all numbers to {@link BigDecimal} and
* then operates on them, and also keeps the result as a {@link BigDecimal}. This is FreeMarker's default arithmetic
* engine.
*/
public static final BigDecimalEngine BIGDECIMAL_ENGINE = new BigDecimalEngine();
/**
* Arithmetic engine that uses (more-or-less) the widening conversions of
* Java language to determine the type of result of operation, instead of
* converting everything to BigDecimal up front.
*/
public static final ConservativeEngine CONSERVATIVE_ENGINE = new ConservativeEngine();
public abstract int compareNumbers(Number first, Number second) throws TemplateException;
public abstract Number add(Number first, Number second) throws TemplateException;
public abstract Number subtract(Number first, Number second) throws TemplateException;
public abstract Number multiply(Number first, Number second) throws TemplateException;
public abstract Number divide(Number first, Number second) throws TemplateException;
public abstract Number modulus(Number first, Number second) throws TemplateException;
/**
* Should be able to parse all FTL numerical literals, Java Double toString results, and XML Schema numbers.
* This means these should be parsed successfully, except if the arithmetical engine
* couldn't support the resulting value anyway (such as NaN, infinite, even non-integers):
* {@code -123.45}, {@code 1.5e3}, {@code 1.5E3}, {@code 0005}, {@code +0}, {@code -0}, {@code NaN},
* {@code INF}, {@code -INF}, {@code Infinity}, {@code -Infinity}.
*/
public abstract Number toNumber(String s);
protected int minScale = 12;
protected int maxScale = 12;
protected int roundingPolicy = BigDecimal.ROUND_HALF_UP;
/**
* Sets the minimal scale to use when dividing BigDecimal numbers. Default
* value is 12.
*/
public void setMinScale(int minScale) {
if (minScale < 0) {
throw new IllegalArgumentException("minScale < 0");
}
this.minScale = minScale;
}
/**
* Sets the maximal scale to use when multiplying BigDecimal numbers.
* Default value is 100.
*/
public void setMaxScale(int maxScale) {
if (maxScale < minScale) {
throw new IllegalArgumentException("maxScale < minScale");
}
this.maxScale = maxScale;
}
public void setRoundingPolicy(int roundingPolicy) {
if (roundingPolicy != BigDecimal.ROUND_CEILING
&& roundingPolicy != BigDecimal.ROUND_DOWN
&& roundingPolicy != BigDecimal.ROUND_FLOOR
&& roundingPolicy != BigDecimal.ROUND_HALF_DOWN
&& roundingPolicy != BigDecimal.ROUND_HALF_EVEN
&& roundingPolicy != BigDecimal.ROUND_HALF_UP
&& roundingPolicy != BigDecimal.ROUND_UNNECESSARY
&& roundingPolicy != BigDecimal.ROUND_UP) {
throw new IllegalArgumentException("invalid rounding policy");
}
this.roundingPolicy = roundingPolicy;
}
/**
* This is the default arithmetic engine in FreeMarker. It converts every
* number it receives into {@link BigDecimal}, then operates on these
* converted {@link BigDecimal}s.
*/
public static class BigDecimalEngine extends ArithmeticEngine {
@Override
public int compareNumbers(Number first, Number second) {
// We try to find the result based on the sign (+/-/0) first, because:
// - It's much faster than converting to BigDecial, and comparing to 0 is the most common comparison.
// - It doesn't require any type conversions, and thus things like "Infinity > 0" won't fail.
int firstSignum = NumberUtil.getSignum(first);
int secondSignum = NumberUtil.getSignum(second);
if (firstSignum != secondSignum) {
return firstSignum < secondSignum ? -1 : (firstSignum > secondSignum ? 1 : 0);
} else if (firstSignum == 0 && secondSignum == 0) {
return 0;
} else {
// The most common case is comparing values of the same type. As BigDecimal can represent all of these
// with loseless round-trip (i.e., converting to BigDecimal and then back the original type gives the
// original value), we can avoid conversion to BigDecimal without changing the result.
if (first.getClass() == second.getClass()) {
// Bit of optimization for this is a very common case:
if (first instanceof BigDecimal) {
return ((BigDecimal) first).compareTo((BigDecimal) second);
}
if (first instanceof Integer) {
return ((Integer) first).compareTo((Integer) second);
}
if (first instanceof Long) {
return ((Long) first).compareTo((Long) second);
}
if (first instanceof Double) {
return ((Double) first).compareTo((Double) second);
}
if (first instanceof Float) {
return ((Float) first).compareTo((Float) second);
}
if (first instanceof Byte) {
return ((Byte) first).compareTo((Byte) second);
}
if (first instanceof Short) {
return ((Short) first).compareTo((Short) second);
}
}
// We are going to compare values of two different types.
// Handle infinity before we try conversion to BigDecimal, as that BigDecimal can't represent that:
if (first instanceof Double) {
double firstD = first.doubleValue();
if (Double.isInfinite(firstD)) {
if (NumberUtil.hasTypeThatIsKnownToNotSupportInfiniteAndNaN(second)) {
return firstD == Double.NEGATIVE_INFINITY ? -1 : 1;
}
if (second instanceof Float) {
return Double.compare(firstD, second.doubleValue());
}
}
}
if (first instanceof Float) {
float firstF = first.floatValue();
if (Float.isInfinite(firstF)) {
if (NumberUtil.hasTypeThatIsKnownToNotSupportInfiniteAndNaN(second)) {
return firstF == Float.NEGATIVE_INFINITY ? -1 : 1;
}
if (second instanceof Double) {
return Double.compare(firstF, second.doubleValue());
}
}
}
if (second instanceof Double) {
double secondD = second.doubleValue();
if (Double.isInfinite(secondD)) {
if (NumberUtil.hasTypeThatIsKnownToNotSupportInfiniteAndNaN(first)) {
return secondD == Double.NEGATIVE_INFINITY ? 1 : -1;
}
if (first instanceof Float) {
return Double.compare(first.doubleValue(), secondD);
}
}
}
if (second instanceof Float) {
float secondF = second.floatValue();
if (Float.isInfinite(secondF)) {
if (NumberUtil.hasTypeThatIsKnownToNotSupportInfiniteAndNaN(first)) {
return secondF == Float.NEGATIVE_INFINITY ? 1 : -1;
}
if (first instanceof Double) {
return Double.compare(first.doubleValue(), secondF);
}
}
}
return toBigDecimal(first).compareTo(toBigDecimal(second));
}
}
@Override
public Number add(Number first, Number second) {
BigDecimal left = toBigDecimal(first);
BigDecimal right = toBigDecimal(second);
return left.add(right);
}
@Override
public Number subtract(Number first, Number second) {
BigDecimal left = toBigDecimal(first);
BigDecimal right = toBigDecimal(second);
return left.subtract(right);
}
@Override
public Number multiply(Number first, Number second) {
BigDecimal left = toBigDecimal(first);
BigDecimal right = toBigDecimal(second);
BigDecimal result = left.multiply(right);
if (result.scale() > maxScale) {
result = result.setScale(maxScale, roundingPolicy);
}
return result;
}
@Override
public Number divide(Number first, Number second) {
BigDecimal left = toBigDecimal(first);
BigDecimal right = toBigDecimal(second);
return divide(left, right);
}
@Override
public Number modulus(Number first, Number second) {
long left = first.longValue();
long right = second.longValue();
return Long.valueOf(left % right);
}
@Override
public Number toNumber(String s) {
return toBigDecimalOrDouble(s);
}
private BigDecimal divide(BigDecimal left, BigDecimal right) {
int scale1 = left.scale();
int scale2 = right.scale();
int scale = Math.max(scale1, scale2);
scale = Math.max(minScale, scale);
return left.divide(right, scale, roundingPolicy);
}
}
/**
* An arithmetic engine that conservatively widens the operation arguments
* to extent that they can hold the result of the operation. Widening
* conversions occur in following situations:
*
* - byte and short are always widened to int (alike to Java language).
* - To preserve magnitude: when operands are of different types, the
* result type is the type of the wider operand.
* - to avoid overflows: if add, subtract, or multiply would overflow on
* integer types, the result is widened from int to long, or from long to
* BigInteger.
* - to preserve fractional part: if a division of integer types would
* have a fractional part, int and long are converted to double, and
* BigInteger is converted to BigDecimal. An operation on a float and a
* long results in a double. An operation on a float or double and a
* BigInteger results in a BigDecimal.
*
*/
public static class ConservativeEngine extends ArithmeticEngine {
private static final int INTEGER = 0;
private static final int LONG = 1;
private static final int FLOAT = 2;
private static final int DOUBLE = 3;
private static final int BIGINTEGER = 4;
private static final int BIGDECIMAL = 5;
private static final Map classCodes = createClassCodesMap();
@Override
public int compareNumbers(Number first, Number second) throws TemplateException {
switch(getCommonClassCode(first, second)) {
case INTEGER: {
int n1 = first.intValue();
int n2 = second.intValue();
return n1 < n2 ? -1 : (n1 == n2 ? 0 : 1);
}
case LONG: {
long n1 = first.longValue();
long n2 = second.longValue();
return n1 < n2 ? -1 : (n1 == n2 ? 0 : 1);
}
case FLOAT: {
float n1 = first.floatValue();
float n2 = second.floatValue();
return n1 < n2 ? -1 : (n1 == n2 ? 0 : 1);
}
case DOUBLE: {
double n1 = first.doubleValue();
double n2 = second.doubleValue();
return n1 < n2 ? -1 : (n1 == n2 ? 0 : 1);
}
case BIGINTEGER: {
BigInteger n1 = toBigInteger(first);
BigInteger n2 = toBigInteger(second);
return n1.compareTo(n2);
}
case BIGDECIMAL: {
BigDecimal n1 = toBigDecimal(first);
BigDecimal n2 = toBigDecimal(second);
return n1.compareTo(n2);
}
}
// Make the compiler happy. getCommonClassCode() is guaranteed to
// return only above codes, or throw an exception.
throw new Error();
}
@Override
public Number add(Number first, Number second) throws TemplateException {
switch(getCommonClassCode(first, second)) {
case INTEGER: {
int n1 = first.intValue();
int n2 = second.intValue();
int n = n1 + n2;
return
((n ^ n1) < 0 && (n ^ n2) < 0) // overflow check
? Long.valueOf(((long) n1) + n2)
: Integer.valueOf(n);
}
case LONG: {
long n1 = first.longValue();
long n2 = second.longValue();
long n = n1 + n2;
return
((n ^ n1) < 0 && (n ^ n2) < 0) // overflow check
? toBigInteger(first).add(toBigInteger(second))
: Long.valueOf(n);
}
case FLOAT: {
return Float.valueOf(first.floatValue() + second.floatValue());
}
case DOUBLE: {
return Double.valueOf(first.doubleValue() + second.doubleValue());
}
case BIGINTEGER: {
BigInteger n1 = toBigInteger(first);
BigInteger n2 = toBigInteger(second);
return n1.add(n2);
}
case BIGDECIMAL: {
BigDecimal n1 = toBigDecimal(first);
BigDecimal n2 = toBigDecimal(second);
return n1.add(n2);
}
}
// Make the compiler happy. getCommonClassCode() is guaranteed to
// return only above codes, or throw an exception.
throw new Error();
}
@Override
public Number subtract(Number first, Number second) throws TemplateException {
switch(getCommonClassCode(first, second)) {
case INTEGER: {
int n1 = first.intValue();
int n2 = second.intValue();
int n = n1 - n2;
return
((n ^ n1) < 0 && (n ^ ~n2) < 0) // overflow check
? (Number) Long.valueOf(((long) n1) - n2)
: (Number) Integer.valueOf(n);
}
case LONG: {
long n1 = first.longValue();
long n2 = second.longValue();
long n = n1 - n2;
return
((n ^ n1) < 0 && (n ^ ~n2) < 0) // overflow check
? (Number) toBigInteger(first).subtract(toBigInteger(second))
: (Number) Long.valueOf(n);
}
case FLOAT: {
return Float.valueOf(first.floatValue() - second.floatValue());
}
case DOUBLE: {
return Double.valueOf(first.doubleValue() - second.doubleValue());
}
case BIGINTEGER: {
BigInteger n1 = toBigInteger(first);
BigInteger n2 = toBigInteger(second);
return n1.subtract(n2);
}
case BIGDECIMAL: {
BigDecimal n1 = toBigDecimal(first);
BigDecimal n2 = toBigDecimal(second);
return n1.subtract(n2);
}
}
// Make the compiler happy. getCommonClassCode() is guaranteed to
// return only above codes, or throw an exception.
throw new Error();
}
@Override
public Number multiply(Number first, Number second) throws TemplateException {
switch(getCommonClassCode(first, second)) {
case INTEGER: {
int n1 = first.intValue();
int n2 = second.intValue();
int n = n1 * n2;
return
n1 == 0 || n / n1 == n2 // overflow check
? (Number) Integer.valueOf(n)
: (Number) Long.valueOf(((long) n1) * n2);
}
case LONG: {
long n1 = first.longValue();
long n2 = second.longValue();
long n = n1 * n2;
return
n1 == 0L || n / n1 == n2 // overflow check
? (Number) Long.valueOf(n)
: (Number) toBigInteger(first).multiply(toBigInteger(second));
}
case FLOAT: {
return Float.valueOf(first.floatValue() * second.floatValue());
}
case DOUBLE: {
return Double.valueOf(first.doubleValue() * second.doubleValue());
}
case BIGINTEGER: {
BigInteger n1 = toBigInteger(first);
BigInteger n2 = toBigInteger(second);
return n1.multiply(n2);
}
case BIGDECIMAL: {
BigDecimal n1 = toBigDecimal(first);
BigDecimal n2 = toBigDecimal(second);
BigDecimal r = n1.multiply(n2);
return r.scale() > maxScale ? r.setScale(maxScale, roundingPolicy) : r;
}
}
// Make the compiler happy. getCommonClassCode() is guaranteed to
// return only above codes, or throw an exception.
throw new Error();
}
@Override
public Number divide(Number first, Number second) throws TemplateException {
switch(getCommonClassCode(first, second)) {
case INTEGER: {
int n1 = first.intValue();
int n2 = second.intValue();
if (n1 % n2 == 0) {
return Integer.valueOf(n1 / n2);
}
return Double.valueOf(((double) n1) / n2);
}
case LONG: {
long n1 = first.longValue();
long n2 = second.longValue();
if (n1 % n2 == 0) {
return Long.valueOf(n1 / n2);
}
return Double.valueOf(((double) n1) / n2);
}
case FLOAT: {
return Float.valueOf(first.floatValue() / second.floatValue());
}
case DOUBLE: {
return Double.valueOf(first.doubleValue() / second.doubleValue());
}
case BIGINTEGER: {
BigInteger n1 = toBigInteger(first);
BigInteger n2 = toBigInteger(second);
BigInteger[] divmod = n1.divideAndRemainder(n2);
if (divmod[1].equals(BigInteger.ZERO)) {
return divmod[0];
} else {
BigDecimal bd1 = new BigDecimal(n1);
BigDecimal bd2 = new BigDecimal(n2);
return bd1.divide(bd2, minScale, roundingPolicy);
}
}
case BIGDECIMAL: {
BigDecimal n1 = toBigDecimal(first);
BigDecimal n2 = toBigDecimal(second);
int scale1 = n1.scale();
int scale2 = n2.scale();
int scale = Math.max(scale1, scale2);
scale = Math.max(minScale, scale);
return n1.divide(n2, scale, roundingPolicy);
}
}
// Make the compiler happy. getCommonClassCode() is guaranteed to
// return only above codes, or throw an exception.
throw new Error();
}
@Override
public Number modulus(Number first, Number second) throws TemplateException {
switch(getCommonClassCode(first, second)) {
case INTEGER: {
return Integer.valueOf(first.intValue() % second.intValue());
}
case LONG: {
return Long.valueOf(first.longValue() % second.longValue());
}
case FLOAT: {
return Float.valueOf(first.floatValue() % second.floatValue());
}
case DOUBLE: {
return Double.valueOf(first.doubleValue() % second.doubleValue());
}
case BIGINTEGER: {
BigInteger n1 = toBigInteger(first);
BigInteger n2 = toBigInteger(second);
return n1.mod(n2);
}
case BIGDECIMAL: {
throw new _MiscTemplateException("Can't calculate remainder on BigDecimals");
}
}
// Make the compiler happy. getCommonClassCode() is guaranteed to
// return only above codes, or throw an exception.
throw new BugException();
}
@Override
public Number toNumber(String s) {
Number n = toBigDecimalOrDouble(s);
return n instanceof BigDecimal ? OptimizerUtil.optimizeNumberRepresentation(n) : n;
}
private static Map createClassCodesMap() {
Map map = new HashMap(17);
Integer intcode = Integer.valueOf(INTEGER);
map.put(Byte.class, intcode);
map.put(Short.class, intcode);
map.put(Integer.class, intcode);
map.put(Long.class, Integer.valueOf(LONG));
map.put(Float.class, Integer.valueOf(FLOAT));
map.put(Double.class, Integer.valueOf(DOUBLE));
map.put(BigInteger.class, Integer.valueOf(BIGINTEGER));
map.put(BigDecimal.class, Integer.valueOf(BIGDECIMAL));
return map;
}
private static int getClassCode(Number num) throws TemplateException {
try {
return ((Integer) classCodes.get(num.getClass())).intValue();
} catch (NullPointerException e) {
if (num == null) {
throw new _MiscTemplateException("The Number object was null.");
} else {
throw new _MiscTemplateException("Unknown number type ", num.getClass().getName());
}
}
}
private static int getCommonClassCode(Number num1, Number num2) throws TemplateException {
int c1 = getClassCode(num1);
int c2 = getClassCode(num2);
int c = c1 > c2 ? c1 : c2;
// If BigInteger is combined with a Float or Double, the result is a
// BigDecimal instead of BigInteger in order not to lose the
// fractional parts. If Float is combined with Long, the result is a
// Double instead of Float to preserve the bigger bit width.
switch(c) {
case FLOAT: {
if ((c1 < c2 ? c1 : c2) == LONG) {
return DOUBLE;
}
break;
}
case BIGINTEGER: {
int min = c1 < c2 ? c1 : c2;
if (min == DOUBLE || min == FLOAT) {
return BIGDECIMAL;
}
break;
}
}
return c;
}
private static BigInteger toBigInteger(Number num) {
return num instanceof BigInteger ? (BigInteger) num : new BigInteger(num.toString());
}
}
/**
* Convert a {@code Number} to {@link BigDecimal}.
*
* @throws NumberFormatException
* If the conversion is not possible, e.g. Infinite and NaN can't be converted to {@link BigDecimal}.
*/
private static BigDecimal toBigDecimal(Number num) {
if (num instanceof BigDecimal) {
return (BigDecimal) num;
}
if (num instanceof Integer || num instanceof Long || num instanceof Byte || num instanceof Short) {
return BigDecimal.valueOf(num.longValue());
}
if (num instanceof BigInteger) {
return new BigDecimal((BigInteger) num);
}
try {
// Why toString? It's partly for backward compatibility. But it's also better for Double (and Float) values
// than new BigDecimal(someDouble), which is overly precise. For example, if you have `double d = 0.1`, then
// `new BigDecimal(d).equals(new BigDecimal("0.1"))` is `false`, while
// `new BigDecimal(Double.toString(d)).equals(new BigDecimal("0.1"))` is `true`.
return new BigDecimal(num.toString());
} catch (NumberFormatException e) {
if (NumberUtil.isInfinite(num)) {
throw new NumberFormatException("It's impossible to convert an infinte value ("
+ num.getClass().getSimpleName() + " " + num + ") to BigDecimal.");
}
// The exception message is useless, so we add a new one:
throw new NumberFormatException("Can't parse this as BigDecimal number: " + StringUtil.jQuote(num));
}
}
private static Number toBigDecimalOrDouble(String s) {
if (s.length() > 2) {
char c = s.charAt(0);
if (c == 'I' && (s.equals("INF") || s.equals("Infinity"))) {
return Double.valueOf(Double.POSITIVE_INFINITY);
} else if (c == 'N' && s.equals("NaN")) {
return Double.valueOf(Double.NaN);
} else if (c == '-' && s.charAt(1) == 'I' && (s.equals("-INF") || s.equals("-Infinity"))) {
return Double.valueOf(Double.NEGATIVE_INFINITY);
}
}
return new BigDecimal(s);
}
}