freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [2/2] incubator-freemarker git commit: ArithmeticEngine related classes were moved to org.apache.freemarker.core.arithmetic.
Date Sat, 25 Feb 2017 07:14:02 GMT
ArithmeticEngine related classes were moved to org.apache.freemarker.core.arithmetic.


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/dcb0e063
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/dcb0e063
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/dcb0e063

Branch: refs/heads/3
Commit: dcb0e0634aeabc0221c9e89126f819c3aa60560d
Parents: 7308532
Author: ddekany <ddekany@apache.org>
Authored: Sat Feb 25 08:13:52 2017 +0100
Committer: ddekany <ddekany@apache.org>
Committed: Sat Feb 25 08:13:52 2017 +0100

----------------------------------------------------------------------
 .../freemarker/core/ASTExpAddOrConcat.java      |   1 +
 .../freemarker/core/ASTExpNegateOrPlus.java     |   5 +-
 .../freemarker/core/ArithmeticEngine.java       | 549 -------------------
 .../freemarker/core/ArithmeticExpression.java   |   1 +
 .../freemarker/core/BuiltInsForSequences.java   |   1 +
 .../apache/freemarker/core/Configurable.java    |  17 +-
 .../freemarker/core/ParserConfiguration.java    |   1 +
 .../freemarker/core/TemplateConfiguration.java  |   1 +
 .../org/apache/freemarker/core/_EvalUtil.java   |   4 +-
 ..._ParserConfigurationWithInheritedFormat.java |   1 +
 .../core/arithmetic/ArithmeticEngine.java       |  93 ++++
 .../impl/BigDecimalArithmeticEngine.java        | 107 ++++
 .../impl/ConservativeArithmeticEngine.java      | 381 +++++++++++++
 .../core/model/TemplateNumberModel.java         |   2 +-
 .../freemarker/core/util/_NumberUtil.java       |  23 +
 .../core/valueformat/TemplateNumberFormat.java  |   2 +-
 src/manual/en_US/FM3-CHANGE-LOG.txt             |   1 +
 .../core/ObjectBuilderSettingsTest.java         |   4 +-
 .../core/TemplateConfigurationTest.java         |   4 +-
 19 files changed, 634 insertions(+), 564 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java b/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java
index d37fa14..48a1fa3 100644
--- a/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java
+++ b/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java
@@ -22,6 +22,7 @@ package org.apache.freemarker.core;
 import java.util.HashSet;
 import java.util.Set;
 
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.model.TemplateCollectionModel;
 import org.apache.freemarker.core.model.TemplateHashModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/main/java/org/apache/freemarker/core/ASTExpNegateOrPlus.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ASTExpNegateOrPlus.java b/src/main/java/org/apache/freemarker/core/ASTExpNegateOrPlus.java
index f007442..233d89d 100644
--- a/src/main/java/org/apache/freemarker/core/ASTExpNegateOrPlus.java
+++ b/src/main/java/org/apache/freemarker/core/ASTExpNegateOrPlus.java
@@ -19,6 +19,8 @@
 
 package org.apache.freemarker.core;
 
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.arithmetic.impl.ConservativeArithmeticEngine;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateNumberModel;
 import org.apache.freemarker.core.model.impl.SimpleNumber;
@@ -54,7 +56,8 @@ final class ASTExpNegateOrPlus extends ASTExpression {
         }
         target.assertNonNull(targetModel, env);
         Number n = targetModel.getAsNumber();
-        n = ArithmeticEngine.CONSERVATIVE_ENGINE.multiply(MINUS_ONE, n);
+        // [FM3] Add ArithmeticEngine.negate, then use the engine from the env
+        n = ConservativeArithmeticEngine.INSTANCE.multiply(MINUS_ONE, n);
         return new SimpleNumber(n);
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/main/java/org/apache/freemarker/core/ArithmeticEngine.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ArithmeticEngine.java b/src/main/java/org/apache/freemarker/core/ArithmeticEngine.java
deleted file mode 100644
index b3ffe02..0000000
--- a/src/main/java/org/apache/freemarker/core/ArithmeticEngine.java
+++ /dev/null
@@ -1,549 +0,0 @@
-/*
- * 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 org.apache.freemarker.core;
-
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.freemarker.core.util.BugException;
-import org.apache.freemarker.core.util._NumberUtil;
-import org.apache.freemarker.core.util._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
-                        ? 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).subtract(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.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
-                        ? Integer.valueOf(n)
-                        : 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
-                        ? Long.valueOf(n)
-                        : 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 ? _NumberUtil.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);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/main/java/org/apache/freemarker/core/ArithmeticExpression.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ArithmeticExpression.java b/src/main/java/org/apache/freemarker/core/ArithmeticExpression.java
index d075e45..6ceb91a 100644
--- a/src/main/java/org/apache/freemarker/core/ArithmeticExpression.java
+++ b/src/main/java/org/apache/freemarker/core/ArithmeticExpression.java
@@ -19,6 +19,7 @@
 
 package org.apache.freemarker.core;
 
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.impl.SimpleNumber;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java b/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java
index 793eb1e..92c59b8 100644
--- a/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java
+++ b/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java
@@ -27,6 +27,7 @@ import java.util.Comparator;
 import java.util.Date;
 import java.util.List;
 
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.model.Constants;
 import org.apache.freemarker.core.model.TemplateBooleanModel;
 import org.apache.freemarker.core.model.TemplateCollectionModel;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/main/java/org/apache/freemarker/core/Configurable.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/Configurable.java b/src/main/java/org/apache/freemarker/core/Configurable.java
index 52fa6d2..000f4bb 100644
--- a/src/main/java/org/apache/freemarker/core/Configurable.java
+++ b/src/main/java/org/apache/freemarker/core/Configurable.java
@@ -39,6 +39,9 @@ import java.util.Properties;
 import java.util.Set;
 import java.util.TimeZone;
 
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
+import org.apache.freemarker.core.arithmetic.impl.ConservativeArithmeticEngine;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
@@ -64,11 +67,7 @@ import org.apache.freemarker.core.templateresolver.PathRegexMatcher;
 import org.apache.freemarker.core.templateresolver.TemplateLoader;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormatFM2;
-import org.apache.freemarker.core.util.FTLUtil;
-import org.apache.freemarker.core.util.GenericParseException;
-import org.apache.freemarker.core.util._NullArgumentException;
-import org.apache.freemarker.core.util._SortedArraySet;
-import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.core.util.*;
 import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
 import org.apache.freemarker.core.valueformat.TemplateNumberFormat;
 import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
@@ -367,7 +366,7 @@ public class Configurable {
         dateTimeFormat = "";
         templateExceptionHandler = _TemplateAPI.getDefaultTemplateExceptionHandler(
                 incompatibleImprovements);
-        arithmeticEngine = ArithmeticEngine.BIGDECIMAL_ENGINE;
+        arithmeticEngine = BigDecimalArithmeticEngine.INSTANCE;
         objectWrapper = Configuration.getDefaultObjectWrapper(incompatibleImprovements);
         autoFlush = Boolean.TRUE;
         newBuiltinClassResolver = TemplateClassResolver.UNRESTRICTED_RESOLVER;
@@ -1145,7 +1144,7 @@ public class Configurable {
 
     /**
      * Sets the arithmetic engine used to perform arithmetic operations.
-     * The default is {@link ArithmeticEngine#BIGDECIMAL_ENGINE}.
+     * The default is {@link BigDecimalArithmeticEngine#INSTANCE}.
      */
     public void setArithmeticEngine(ArithmeticEngine arithmeticEngine) {
         _NullArgumentException.check("arithmeticEngine", arithmeticEngine);
@@ -2193,9 +2192,9 @@ public class Configurable {
             } else if (ARITHMETIC_ENGINE_KEY_SNAKE_CASE.equals(name) || ARITHMETIC_ENGINE_KEY_CAMEL_CASE.equals(name)) {
                 if (value.indexOf('.') == -1) { 
                     if ("bigdecimal".equalsIgnoreCase(value)) {
-                        setArithmeticEngine(ArithmeticEngine.BIGDECIMAL_ENGINE);
+                        setArithmeticEngine(BigDecimalArithmeticEngine.INSTANCE);
                     } else if ("conservative".equalsIgnoreCase(value)) {
-                        setArithmeticEngine(ArithmeticEngine.CONSERVATIVE_ENGINE);
+                        setArithmeticEngine(ConservativeArithmeticEngine.INSTANCE);
                     } else {
                         throw invalidSettingValueException(name, value);
                     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/main/java/org/apache/freemarker/core/ParserConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/ParserConfiguration.java b/src/main/java/org/apache/freemarker/core/ParserConfiguration.java
index a72ba36..0ed1424 100644
--- a/src/main/java/org/apache/freemarker/core/ParserConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/ParserConfiguration.java
@@ -18,6 +18,7 @@
  */
 package org.apache.freemarker.core;
 
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.outputformat.OutputFormat;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
index aaea96e..7b34a70 100644
--- a/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
@@ -26,6 +26,7 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.TimeZone;
 
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.outputformat.OutputFormat;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/main/java/org/apache/freemarker/core/_EvalUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/_EvalUtil.java b/src/main/java/org/apache/freemarker/core/_EvalUtil.java
index fb0e7a5..f068774 100644
--- a/src/main/java/org/apache/freemarker/core/_EvalUtil.java
+++ b/src/main/java/org/apache/freemarker/core/_EvalUtil.java
@@ -21,6 +21,8 @@ package org.apache.freemarker.core;
 
 import java.util.Date;
 
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
 import org.apache.freemarker.core.model.TemplateBooleanModel;
 import org.apache.freemarker.core.model.TemplateCollectionModel;
 import org.apache.freemarker.core.model.TemplateDateModel;
@@ -219,7 +221,7 @@ public class _EvalUtil {
                         ? env.getArithmeticEngine()
                         : (leftExp != null
                             ? leftExp.getTemplate().getArithmeticEngine()
-                            : ArithmeticEngine.BIGDECIMAL_ENGINE);
+                            : BigDecimalArithmeticEngine.INSTANCE);
             try {
                 cmpResult = ae.compareNumbers(leftNum, rightNum);
             } catch (RuntimeException e) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java b/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java
index 1360d43..cf239fa 100644
--- a/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java
+++ b/src/main/java/org/apache/freemarker/core/_ParserConfigurationWithInheritedFormat.java
@@ -18,6 +18,7 @@
  */
 package org.apache.freemarker.core;
 
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.outputformat.OutputFormat;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/main/java/org/apache/freemarker/core/arithmetic/ArithmeticEngine.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/arithmetic/ArithmeticEngine.java b/src/main/java/org/apache/freemarker/core/arithmetic/ArithmeticEngine.java
new file mode 100644
index 0000000..43fc3c3
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/arithmetic/ArithmeticEngine.java
@@ -0,0 +1,93 @@
+/*
+ * 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 org.apache.freemarker.core.arithmetic;
+
+import java.math.BigDecimal;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Implements the arithmetic operations executed by the template language; see
+ * {@link Configuration#setArithmeticEngine(ArithmeticEngine)}.
+ */
+public abstract class ArithmeticEngine {
+
+    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;
+    // [FM3] Add negate (should keep the Number type even for BigDecimalArithmeticEngine, unlike multiply). Then fix
+    // the negate operation in the template language.
+
+    /**
+     * 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;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/main/java/org/apache/freemarker/core/arithmetic/impl/BigDecimalArithmeticEngine.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/arithmetic/impl/BigDecimalArithmeticEngine.java b/src/main/java/org/apache/freemarker/core/arithmetic/impl/BigDecimalArithmeticEngine.java
new file mode 100644
index 0000000..ade8ff7
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/arithmetic/impl/BigDecimalArithmeticEngine.java
@@ -0,0 +1,107 @@
+/*
+ * 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 org.apache.freemarker.core.arithmetic.impl;
+
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.util._NumberUtil;
+
+import java.math.BigDecimal;
+
+/**
+ * Arithmetic engine that converts all numbers to {@link BigDecimal} and
+ * then operates on them. This is FreeMarker's default arithmetic engine.
+ */
+public class BigDecimalArithmeticEngine extends ArithmeticEngine {
+
+    public static final BigDecimalArithmeticEngine INSTANCE = new BigDecimalArithmeticEngine();
+
+    protected BigDecimalArithmeticEngine() {
+        //
+    }
+
+    @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 = _NumberUtil.toBigDecimal(first);
+            BigDecimal right = _NumberUtil.toBigDecimal(second);
+            return left.compareTo(right);
+        }
+    }
+
+    @Override
+    public Number add(Number first, Number second) {
+        BigDecimal left = _NumberUtil.toBigDecimal(first);
+        BigDecimal right = _NumberUtil.toBigDecimal(second);
+        return left.add(right);
+    }
+
+    @Override
+    public Number subtract(Number first, Number second) {
+        BigDecimal left = _NumberUtil.toBigDecimal(first);
+        BigDecimal right = _NumberUtil.toBigDecimal(second);
+        return left.subtract(right);
+    }
+
+    @Override
+    public Number multiply(Number first, Number second) {
+        BigDecimal left = _NumberUtil.toBigDecimal(first);
+        BigDecimal right = _NumberUtil.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 = _NumberUtil.toBigDecimal(first);
+        BigDecimal right = _NumberUtil.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 _NumberUtil.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);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/main/java/org/apache/freemarker/core/arithmetic/impl/ConservativeArithmeticEngine.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/arithmetic/impl/ConservativeArithmeticEngine.java b/src/main/java/org/apache/freemarker/core/arithmetic/impl/ConservativeArithmeticEngine.java
new file mode 100644
index 0000000..45f9509
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/arithmetic/impl/ConservativeArithmeticEngine.java
@@ -0,0 +1,381 @@
+/*
+ * 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 org.apache.freemarker.core.arithmetic.impl;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core._MiscTemplateException;
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util._NumberUtil;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 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.
+ * <p>
+ * 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>
+ */
+// [FM3] Review
+public class ConservativeArithmeticEngine extends ArithmeticEngine {
+
+    public static final ConservativeArithmeticEngine INSTANCE = new ConservativeArithmeticEngine();
+
+    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 BIG_INTEGER = 4;
+    private static final int BIG_DECIMAL = 5;
+
+    private static final Map classCodes = createClassCodesMap();
+
+    protected ConservativeArithmeticEngine() {
+        //
+    }
+
+    @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 BIG_INTEGER: {
+                BigInteger n1 = toBigInteger(first);
+                BigInteger n2 = toBigInteger(second);
+                return n1.compareTo(n2);
+            }
+            case BIG_DECIMAL: {
+                BigDecimal n1 = _NumberUtil.toBigDecimal(first);
+                BigDecimal n2 = _NumberUtil.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 BIG_INTEGER: {
+                BigInteger n1 = toBigInteger(first);
+                BigInteger n2 = toBigInteger(second);
+                return n1.add(n2);
+            }
+            case BIG_DECIMAL: {
+                BigDecimal n1 = _NumberUtil.toBigDecimal(first);
+                BigDecimal n2 = _NumberUtil.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
+                    ? 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).subtract(toBigInteger(second))
+                    : Long.valueOf(n);
+            }
+            case FLOAT: {
+                return Float.valueOf(first.floatValue() - second.floatValue());
+            }
+            case DOUBLE: {
+                return Double.valueOf(first.doubleValue() - second.doubleValue());
+            }
+            case BIG_INTEGER: {
+                BigInteger n1 = toBigInteger(first);
+                BigInteger n2 = toBigInteger(second);
+                return n1.subtract(n2);
+            }
+            case BIG_DECIMAL: {
+                BigDecimal n1 = _NumberUtil.toBigDecimal(first);
+                BigDecimal n2 = _NumberUtil.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
+                    ? Integer.valueOf(n)
+                    : 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
+                    ? Long.valueOf(n)
+                    : toBigInteger(first).multiply(toBigInteger(second));
+            }
+            case FLOAT: {
+                return Float.valueOf(first.floatValue() * second.floatValue());
+            }
+            case DOUBLE: {
+                return Double.valueOf(first.doubleValue() * second.doubleValue());
+            }
+            case BIG_INTEGER: {
+                BigInteger n1 = toBigInteger(first);
+                BigInteger n2 = toBigInteger(second);
+                return n1.multiply(n2);
+            }
+            case BIG_DECIMAL: {
+                BigDecimal n1 = _NumberUtil.toBigDecimal(first);
+                BigDecimal n2 = _NumberUtil.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 BIG_INTEGER: {
+                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 BIG_DECIMAL: {
+                BigDecimal n1 = _NumberUtil.toBigDecimal(first);
+                BigDecimal n2 = _NumberUtil.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 BIG_INTEGER: {
+                BigInteger n1 = toBigInteger(first);
+                BigInteger n2 = toBigInteger(second);
+                return n1.mod(n2);
+            }
+            case BIG_DECIMAL: {
+                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 = _NumberUtil.toBigDecimalOrDouble(s);
+        return n instanceof BigDecimal ? _NumberUtil.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(BIG_INTEGER));
+        map.put(BigDecimal.class, Integer.valueOf(BIG_DECIMAL));
+        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 BIG_INTEGER: {
+                int min = c1 < c2 ? c1 : c2;
+                if (min == DOUBLE || min == FLOAT) {
+                    return BIG_DECIMAL;
+                }
+                break;
+            }
+        }
+        return c;
+    }
+
+    private static BigInteger toBigInteger(Number num) {
+        return num instanceof BigInteger ? (BigInteger) num : new BigInteger(num.toString());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/main/java/org/apache/freemarker/core/model/TemplateNumberModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/model/TemplateNumberModel.java b/src/main/java/org/apache/freemarker/core/model/TemplateNumberModel.java
index 3d1695c..6ca340a 100644
--- a/src/main/java/org/apache/freemarker/core/model/TemplateNumberModel.java
+++ b/src/main/java/org/apache/freemarker/core/model/TemplateNumberModel.java
@@ -19,7 +19,7 @@
 
 package org.apache.freemarker.core.model;
 
-import org.apache.freemarker.core.ArithmeticEngine;
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 
 /**
  * "number" template language data type; an object that stores a number. There's only one numerical type as far as the

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/main/java/org/apache/freemarker/core/util/_NumberUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/util/_NumberUtil.java b/src/main/java/org/apache/freemarker/core/util/_NumberUtil.java
index 0e9825a..1b743f7 100644
--- a/src/main/java/org/apache/freemarker/core/util/_NumberUtil.java
+++ b/src/main/java/org/apache/freemarker/core/util/_NumberUtil.java
@@ -202,4 +202,27 @@ public class _NumberUtil {
         }
         return number;
     }
+
+    public 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));
+        }
+    }
+
+    public 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);
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormat.java b/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormat.java
index 44068e4..de454c9 100644
--- a/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormat.java
+++ b/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormat.java
@@ -20,7 +20,7 @@ package org.apache.freemarker.core.valueformat;
 
 import java.text.NumberFormat;
 
-import org.apache.freemarker.core.ArithmeticEngine;
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.model.TemplateDateModel;
 import org.apache.freemarker.core.model.TemplateMarkupOutputModel;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/manual/en_US/FM3-CHANGE-LOG.txt
----------------------------------------------------------------------
diff --git a/src/manual/en_US/FM3-CHANGE-LOG.txt b/src/manual/en_US/FM3-CHANGE-LOG.txt
index 94b62ad..2f2ac35 100644
--- a/src/manual/en_US/FM3-CHANGE-LOG.txt
+++ b/src/manual/en_US/FM3-CHANGE-LOG.txt
@@ -72,6 +72,7 @@ the FreeMarer 3 changelog here:
   TemplateResolver, which is the central class of loading and caching and template name rules).
   OutputFormat realted classes were moved to org.apache.freemarker.core.outputformat.
   ValueFormat related classes were moved to org.apache.freemarker.core.valueformat.
+  ArithmeticEngine related classes were moved to org.apache.freemarker.core.arithmetic.
   freemarker.ext.beans were moved under org.apache.freemarker.core.model.impl.beans for now (but later
   we only want a DefaultObject wrapper, no BeansWrapper, so this will change) and freemarker.ext.dom
   was moved to org.apache.freemarker.core.model.impl.dom.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java b/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java
index 67336e0..7b21b54 100644
--- a/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java
+++ b/src/test/java/org/apache/freemarker/core/ObjectBuilderSettingsTest.java
@@ -46,6 +46,8 @@ import java.util.Map;
 import java.util.Properties;
 import java.util.TimeZone;
 
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 import org.apache.freemarker.core.model.impl.beans.BeansWrapper;
@@ -447,7 +449,7 @@ public class ObjectBuilderSettingsTest {
             props.setProperty(Configurable.TEMPLATE_EXCEPTION_HANDLER_KEY, "rethrow");
             cfg.setSettings(props);
             assertEquals(BeansWrapper.class, cfg.getObjectWrapper().getClass());
-            assertSame(ArithmeticEngine.BIGDECIMAL_ENGINE, cfg.getArithmeticEngine());
+            assertSame(BigDecimalArithmeticEngine.INSTANCE, cfg.getArithmeticEngine());
             assertSame(TemplateExceptionHandler.RETHROW_HANDLER, cfg.getTemplateExceptionHandler());
             assertTrue(((WriteProtectable) cfg.getObjectWrapper()).isWriteProtected());
             assertEquals(Configuration.VERSION_3_0_0,

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dcb0e063/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java b/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
index 1348ac0..825e92d 100644
--- a/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
+++ b/src/test/java/org/apache/freemarker/core/TemplateConfigurationTest.java
@@ -49,6 +49,8 @@ import java.util.Set;
 import java.util.TimeZone;
 
 import org.apache.commons.collections.ListUtils;
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.arithmetic.impl.ConservativeArithmeticEngine;
 import org.apache.freemarker.core.model.impl.SimpleObjectWrapper;
 import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
 import org.apache.freemarker.core.outputformat.impl.UndefinedOutputFormat;
@@ -183,7 +185,7 @@ public class TemplateConfigurationTest {
         SETTING_ASSIGNMENTS.put("templateExceptionHandler", TemplateExceptionHandler.IGNORE_HANDLER);
         SETTING_ASSIGNMENTS.put("timeFormat", "@HH:mm");
         SETTING_ASSIGNMENTS.put("timeZone", NON_DEFAULT_TZ);
-        SETTING_ASSIGNMENTS.put("arithmeticEngine", ArithmeticEngine.CONSERVATIVE_ENGINE);
+        SETTING_ASSIGNMENTS.put("arithmeticEngine", ConservativeArithmeticEngine.INSTANCE);
         SETTING_ASSIGNMENTS.put("customNumberFormats",
                 ImmutableMap.of("dummy", HexTemplateNumberFormatFactory.INSTANCE));
         SETTING_ASSIGNMENTS.put("customDateFormats",


Mime
View raw message