blob: 7ce63474d535ae2b2afd1f2067a06061f617fb9b [file] [log] [blame]
/*
* 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;
/**
* Class to perform arithmetic operations.
*/
public abstract class ArithmeticEngine {
/**
* Arithmetic engine that converts all numbers to {@link BigDecimal} and
* then operates on them. 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 {
BigDecimal left = toBigDecimal(first);
BigDecimal right = toBigDecimal(second);
return left.compareTo(right);
}
}
@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:
* <ul>
* <li>byte and short are always widened to int (alike to Java language).</li>
* <li>To preserve magnitude: when operands are of different types, the
* result type is the type of the wider operand.</li>
* <li>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.</li>
* <li>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.</li>
* </ul>
*/
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());
}
}
private static BigDecimal toBigDecimal(Number num) {
try {
return num instanceof BigDecimal ? (BigDecimal) num : new BigDecimal(num.toString());
} catch (NumberFormatException e) {
// 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);
}
}