KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > freemarker > core > ArithmeticEngine


1 /*
2  * Copyright (c) 2003 The Visigoth Software Society. All rights
3  * reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in
14  * the documentation and/or other materials provided with the
15  * distribution.
16  *
17  * 3. The end-user documentation included with the redistribution, if
18  * any, must include the following acknowledgement:
19  * "This product includes software developed by the
20  * Visigoth Software Society (http://www.visigoths.org/)."
21  * Alternately, this acknowledgement may appear in the software itself,
22  * if and wherever such third-party acknowledgements normally appear.
23  *
24  * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
25  * project contributors may be used to endorse or promote products derived
26  * from this software without prior written permission. For written
27  * permission, please contact visigoths@visigoths.org.
28  *
29  * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
30  * nor may "FreeMarker" or "Visigoth" appear in their names
31  * without prior written permission of the Visigoth Software Society.
32  *
33  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
34  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
35  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
36  * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
37  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
39  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
40  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
41  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
42  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
43  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
44  * SUCH DAMAGE.
45  * ====================================================================
46  *
47  * This software consists of voluntary contributions made by many
48  * individuals on behalf of the Visigoth Software Society. For more
49  * information on the Visigoth Software Society, please see
50  * http://www.visigoths.org/
51  */

52
53 package freemarker.core;
54
55 import java.math.*;
56 import java.util.HashMap JavaDoc;
57 import java.util.Map JavaDoc;
58
59 import freemarker.template.*;
60 import freemarker.template.utility.OptimizerUtil;
61
62 /**
63  * Class to perform arithmetic operations.
64  * @author <a HREF="mailto:jon@revusky.com">Jonathan Revusky</a>
65  * @author Attila Szegedi
66  */

67
68 public abstract class ArithmeticEngine {
69
70     /**
71      * Arithmetic engine that converts all numbers to {@link BigDecimal} and
72      * then operates on them. This is FreeMarker's default arithmetic engine.
73      */

74     public static final BigDecimalEngine BIGDECIMAL_ENGINE = new BigDecimalEngine();
75     /**
76      * Arithmetic engine that uses (more-or-less) the widening conversions of
77      * Java language to determine the type of result of operation, instead of
78      * converting everything to BigDecimal up front.
79      */

80     public static final ConservativeEngine CONSERVATIVE_ENGINE = new ConservativeEngine();
81
82     public abstract int compareNumbers(Number JavaDoc first, Number JavaDoc second) throws TemplateException;
83     public abstract Number JavaDoc add(Number JavaDoc first, Number JavaDoc second) throws TemplateException;
84     public abstract Number JavaDoc subtract(Number JavaDoc first, Number JavaDoc second) throws TemplateException;
85     public abstract Number JavaDoc multiply(Number JavaDoc first, Number JavaDoc second) throws TemplateException;
86     public abstract Number JavaDoc divide(Number JavaDoc first, Number JavaDoc second) throws TemplateException;
87     public abstract Number JavaDoc modulus(Number JavaDoc first, Number JavaDoc second) throws TemplateException;
88     public abstract Number JavaDoc toNumber(String JavaDoc s);
89
90     protected int minScale = 12;
91     protected int maxScale = 12;
92     protected int roundingPolicy = BigDecimal.ROUND_HALF_UP;
93
94     /**
95      * Sets the minimal scale to use when dividing BigDecimal numbers. Default
96      * value is 12.
97      */

98     public void setMinScale(int minScale) {
99         if(minScale < 0) {
100             throw new IllegalArgumentException JavaDoc("minScale < 0");
101         }
102         this.minScale = minScale;
103     }
104     
105     /**
106      * Sets the maximal scale to use when multiplying BigDecimal numbers.
107      * Default value is 100.
108      */

109     public void setMaxScale(int maxScale) {
110         if(maxScale < minScale) {
111             throw new IllegalArgumentException JavaDoc("maxScale < minScale");
112         }
113         this.maxScale = maxScale;
114     }
115
116     public void setRoundingPolicy(int roundingPolicy) {
117         if (roundingPolicy != BigDecimal.ROUND_CEILING
118             && roundingPolicy != BigDecimal.ROUND_DOWN
119             && roundingPolicy != BigDecimal.ROUND_FLOOR
120             && roundingPolicy != BigDecimal.ROUND_HALF_DOWN
121             && roundingPolicy != BigDecimal.ROUND_HALF_EVEN
122             && roundingPolicy != BigDecimal.ROUND_HALF_UP
123             && roundingPolicy != BigDecimal.ROUND_UNNECESSARY
124             && roundingPolicy != BigDecimal.ROUND_UP)
125         {
126             throw new IllegalArgumentException JavaDoc("invalid rounding policy");
127         }
128         
129         this.roundingPolicy = roundingPolicy;
130     }
131
132     /**
133      * This is the default arithmetic engine in FreeMarker. It converts every
134      * number it receives into {@link BigDecimal}, then operates on these
135      * converted {@link BigDecimal}s.
136      */

137     public static class BigDecimalEngine
138     extends
139         ArithmeticEngine
140     {
141         public int compareNumbers(Number JavaDoc first, Number JavaDoc second) {
142             BigDecimal left = toBigDecimal(first);
143             BigDecimal right = toBigDecimal(second);
144             return left.compareTo(right);
145         }
146     
147         public Number JavaDoc add(Number JavaDoc first, Number JavaDoc second) {
148             BigDecimal left = toBigDecimal(first);
149             BigDecimal right = toBigDecimal(second);
150             return left.add(right);
151         }
152     
153         public Number JavaDoc subtract(Number JavaDoc first, Number JavaDoc second) {
154             BigDecimal left = toBigDecimal(first);
155             BigDecimal right = toBigDecimal(second);
156             return left.subtract(right);
157         }
158     
159         public Number JavaDoc multiply(Number JavaDoc first, Number JavaDoc second) {
160             BigDecimal left = toBigDecimal(first);
161             BigDecimal right = toBigDecimal(second);
162             BigDecimal result = left.multiply(right);
163             if (result.scale() > maxScale) {
164                 result = result.setScale(maxScale, roundingPolicy);
165             }
166             return result;
167         }
168     
169         public Number JavaDoc divide(Number JavaDoc first, Number JavaDoc second) {
170             BigDecimal left = toBigDecimal(first);
171             BigDecimal right = toBigDecimal(second);
172             return divide(left, right);
173         }
174     
175         public Number JavaDoc modulus(Number JavaDoc first, Number JavaDoc second) {
176             long left = first.longValue();
177             long right = second.longValue();
178             return new Long JavaDoc(left % right);
179         }
180     
181         public Number JavaDoc toNumber(String JavaDoc s) {
182             return new BigDecimal(s);
183         }
184         
185         private BigDecimal divide(BigDecimal left, BigDecimal right) {
186             int scale1 = left.scale();
187             int scale2 = right.scale();
188             int scale = Math.max(scale1, scale2);
189             scale = Math.max(minScale, scale);
190             return left.divide(right, scale, roundingPolicy);
191         }
192     }
193
194     /**
195      * An arithmetic engine that conservatively widens the operation arguments
196      * to extent that they can hold the result of the operation. Widening
197      * conversions occur in following situations:
198      * <ul>
199      * <li>byte and short are always widened to int (alike to Java language).</li>
200      * <li>To preserve magnitude: when operands are of different types, the
201      * result type is the type of the wider operand.</li>
202      * <li>to avoid overflows: if add, subtract, or multiply would overflow on
203      * integer types, the result is widened from int to long, or from long to
204      * BigInteger.</li>
205      * <li>to preserve fractional part: if a division of integer types would
206      * have a fractional part, int and long are converted to double, and
207      * BigInteger is converted to BigDecimal. An operation on a float and a
208      * long results in a double. An operation on a float or double and a
209      * BigInteger results in a BigDecimal.</li>
210      * </ul>
211      */

212     public static class ConservativeEngine extends ArithmeticEngine {
213         private static final int INTEGER = 0;
214         private static final int LONG = 1;
215         private static final int FLOAT = 2;
216         private static final int DOUBLE = 3;
217         private static final int BIGINTEGER = 4;
218         private static final int BIGDECIMAL = 5;
219         
220         private static final Map JavaDoc classCodes = createClassCodesMap();
221         
222         public int compareNumbers(Number JavaDoc first, Number JavaDoc second) throws TemplateException {
223             switch(getCommonClassCode(first, second)) {
224                 case INTEGER: {
225                     int n1 = first.intValue();
226                     int n2 = second.intValue();
227                     return n1 < n2 ? -1 : (n1 == n2 ? 0 : 1);
228                 }
229                 case LONG: {
230                     long n1 = first.longValue();
231                     long n2 = second.longValue();
232                     return n1 < n2 ? -1 : (n1 == n2 ? 0 : 1);
233                 }
234                 case FLOAT: {
235                     float n1 = first.floatValue();
236                     float n2 = second.floatValue();
237                     return n1 < n2 ? -1 : (n1 == n2 ? 0 : 1);
238                 }
239                 case DOUBLE: {
240                     double n1 = first.doubleValue();
241                     double n2 = second.doubleValue();
242                     return n1 < n2 ? -1 : (n1 == n2 ? 0 : 1);
243                 }
244                 case BIGINTEGER: {
245                     BigInteger n1 = toBigInteger(first);
246                     BigInteger n2 = toBigInteger(second);
247                     return n1.compareTo(n2);
248                 }
249                 case BIGDECIMAL: {
250                     BigDecimal n1 = toBigDecimal(first);
251                     BigDecimal n2 = toBigDecimal(second);
252                     return n1.compareTo(n2);
253                 }
254             }
255             // Make the compiler happy. getCommonClassCode() is guaranteed to
256
// return only above codes, or throw an exception.
257
throw new Error JavaDoc();
258         }
259     
260         public Number JavaDoc add(Number JavaDoc first, Number JavaDoc second) throws TemplateException {
261             switch(getCommonClassCode(first, second)) {
262                 case INTEGER: {
263                     int n1 = first.intValue();
264                     int n2 = second.intValue();
265                     int n = n1 + n2;
266                     return
267                         ((n ^ n1) < 0 && (n ^ n2) < 0) // overflow check
268
? (Number JavaDoc)new Long JavaDoc(((long)n1) + n2)
269                         : (Number JavaDoc)new Integer JavaDoc(n);
270                 }
271                 case LONG: {
272                     long n1 = first.longValue();
273                     long n2 = second.longValue();
274                     long n = n1 + n2;
275                     return
276                         ((n ^ n1) < 0 && (n ^ n2) < 0) // overflow check
277
? (Number JavaDoc)toBigInteger(first).add(toBigInteger(second))
278                         : (Number JavaDoc)new Long JavaDoc(n);
279                 }
280                 case FLOAT: {
281                     return new Float JavaDoc(first.floatValue() + second.floatValue());
282                 }
283                 case DOUBLE: {
284                     return new Double JavaDoc(first.doubleValue() + second.doubleValue());
285                 }
286                 case BIGINTEGER: {
287                     BigInteger n1 = toBigInteger(first);
288                     BigInteger n2 = toBigInteger(second);
289                     return n1.add(n2);
290                 }
291                 case BIGDECIMAL: {
292                     BigDecimal n1 = toBigDecimal(first);
293                     BigDecimal n2 = toBigDecimal(second);
294                     return n1.add(n2);
295                 }
296             }
297             // Make the compiler happy. getCommonClassCode() is guaranteed to
298
// return only above codes, or throw an exception.
299
throw new Error JavaDoc();
300         }
301     
302         public Number JavaDoc subtract(Number JavaDoc first, Number JavaDoc second) throws TemplateException {
303             switch(getCommonClassCode(first, second)) {
304                 case INTEGER: {
305                     int n1 = first.intValue();
306                     int n2 = second.intValue();
307                     int n = n1 - n2;
308                     return
309                         ((n ^ n1) < 0 && (n ^ ~n2) < 0) // overflow check
310
? (Number JavaDoc)new Long JavaDoc(((long)n1) - n2)
311                         : (Number JavaDoc)new Integer JavaDoc(n);
312                 }
313                 case LONG: {
314                     long n1 = first.longValue();
315                     long n2 = second.longValue();
316                     long n = n1 - n2;
317                     return
318                         ((n ^ n1) < 0 && (n ^ ~n2) < 0) // overflow check
319
? (Number JavaDoc)toBigInteger(first).subtract(toBigInteger(second))
320                         : (Number JavaDoc)new Long JavaDoc(n);
321                 }
322                 case FLOAT: {
323                     return new Float JavaDoc(first.floatValue() - second.floatValue());
324                 }
325                 case DOUBLE: {
326                     return new Double JavaDoc(first.doubleValue() - second.doubleValue());
327                 }
328                 case BIGINTEGER: {
329                     BigInteger n1 = toBigInteger(first);
330                     BigInteger n2 = toBigInteger(second);
331                     return n1.subtract(n2);
332                 }
333                 case BIGDECIMAL: {
334                     BigDecimal n1 = toBigDecimal(first);
335                     BigDecimal n2 = toBigDecimal(second);
336                     return n1.subtract(n2);
337                 }
338             }
339             // Make the compiler happy. getCommonClassCode() is guaranteed to
340
// return only above codes, or throw an exception.
341
throw new Error JavaDoc();
342         }
343     
344         public Number JavaDoc multiply(Number JavaDoc first, Number JavaDoc second) throws TemplateException {
345             switch(getCommonClassCode(first, second)) {
346                 case INTEGER: {
347                     int n1 = first.intValue();
348                     int n2 = second.intValue();
349                     int n = n1 * n2;
350                     return
351                         n / n1 == n2 // overflow check
352
? (Number JavaDoc)new Integer JavaDoc(n)
353                         : (Number JavaDoc)new Long JavaDoc(((long)n1) * n2);
354                 }
355                 case LONG: {
356                     long n1 = first.longValue();
357                     long n2 = second.longValue();
358                     long n = n1 * n2;
359                     return
360                         n / n1 == n2 // overflow check
361
? (Number JavaDoc)new Long JavaDoc(n)
362                         : (Number JavaDoc)toBigInteger(first).multiply(toBigInteger(second));
363                 }
364                 case FLOAT: {
365                     return new Float JavaDoc(first.floatValue() * second.floatValue());
366                 }
367                 case DOUBLE: {
368                     return new Double JavaDoc(first.doubleValue() * second.doubleValue());
369                 }
370                 case BIGINTEGER: {
371                     BigInteger n1 = toBigInteger(first);
372                     BigInteger n2 = toBigInteger(second);
373                     return n1.multiply(n2);
374                 }
375                 case BIGDECIMAL: {
376                     BigDecimal n1 = toBigDecimal(first);
377                     BigDecimal n2 = toBigDecimal(second);
378                     BigDecimal r = n1.multiply(n2);
379                     return r.scale() > maxScale ? r.setScale(maxScale, roundingPolicy) : r;
380                 }
381             }
382             // Make the compiler happy. getCommonClassCode() is guaranteed to
383
// return only above codes, or throw an exception.
384
throw new Error JavaDoc();
385         }
386     
387         public Number JavaDoc divide(Number JavaDoc first, Number JavaDoc second) throws TemplateException {
388             switch(getCommonClassCode(first, second)) {
389                 case INTEGER: {
390                     int n1 = first.intValue();
391                     int n2 = second.intValue();
392                     if (n1 % n2 == 0) {
393                         return new Integer JavaDoc(n1/n2);
394                     }
395                     return new Double JavaDoc(((double)n1)/n2);
396                 }
397                 case LONG: {
398                     long n1 = first.longValue();
399                     long n2 = second.longValue();
400                     if (n1 % n2 == 0) {
401                         return new Long JavaDoc(n1/n2);
402                     }
403                     return new Double JavaDoc(((double)n1)/n2);
404                 }
405                 case FLOAT: {
406                     return new Float JavaDoc(first.floatValue() / second.floatValue());
407                 }
408                 case DOUBLE: {
409                     return new Double JavaDoc(first.doubleValue() / second.doubleValue());
410                 }
411                 case BIGINTEGER: {
412                     BigInteger n1 = toBigInteger(first);
413                     BigInteger n2 = toBigInteger(second);
414                     BigInteger[] divmod = n1.divideAndRemainder(n2);
415                     if(divmod[1].equals(BigInteger.ZERO)) {
416                         return divmod[0];
417                     }
418                     else {
419                         BigDecimal bd1 = new BigDecimal(n1);
420                         BigDecimal bd2 = new BigDecimal(n2);
421                         return bd1.divide(bd2, minScale, roundingPolicy);
422                     }
423                 }
424                 case BIGDECIMAL: {
425                     BigDecimal n1 = toBigDecimal(first);
426                     BigDecimal n2 = toBigDecimal(second);
427                     int scale1 = n1.scale();
428                     int scale2 = n2.scale();
429                     int scale = Math.max(scale1, scale2);
430                     scale = Math.max(minScale, scale);
431                     return n1.divide(n2, scale, roundingPolicy);
432                 }
433             }
434             // Make the compiler happy. getCommonClassCode() is guaranteed to
435
// return only above codes, or throw an exception.
436
throw new Error JavaDoc();
437         }
438     
439         public Number JavaDoc modulus(Number JavaDoc first, Number JavaDoc second) throws TemplateException {
440             switch(getCommonClassCode(first, second)) {
441                 case INTEGER: {
442                     return new Integer JavaDoc(first.intValue() % second.intValue());
443                 }
444                 case LONG: {
445                     return new Long JavaDoc(first.longValue() % second.longValue());
446                 }
447                 case FLOAT: {
448                     return new Float JavaDoc(first.floatValue() % second.floatValue());
449                 }
450                 case DOUBLE: {
451                     return new Double JavaDoc(first.doubleValue() % second.doubleValue());
452                 }
453                 case BIGINTEGER: {
454                     BigInteger n1 = toBigInteger(first);
455                     BigInteger n2 = toBigInteger(second);
456                     return n1.mod(n2);
457                 }
458                 case BIGDECIMAL: {
459                     throw new TemplateException("Can't calculate remainder on BigDecimals", Environment.getCurrentEnvironment());
460                 }
461             }
462             // Make the compiler happy. getCommonClassCode() is guaranteed to
463
// return only above codes, or throw an exception.
464
throw new Error JavaDoc();
465         }
466     
467         public Number JavaDoc toNumber(String JavaDoc s) {
468             return OptimizerUtil.optimizeNumberRepresentation(new BigDecimal(s));
469         }
470         
471         private static Map JavaDoc createClassCodesMap() {
472             Map JavaDoc map = new HashMap JavaDoc(17);
473             Integer JavaDoc intcode = new Integer JavaDoc(INTEGER);
474             map.put(Byte JavaDoc.class, intcode);
475             map.put(Short JavaDoc.class, intcode);
476             map.put(Integer JavaDoc.class, intcode);
477             map.put(Long JavaDoc.class, new Integer JavaDoc(LONG));
478             map.put(Float JavaDoc.class, new Integer JavaDoc(FLOAT));
479             map.put(Double JavaDoc.class, new Integer JavaDoc(DOUBLE));
480             map.put(BigInteger.class, new Integer JavaDoc(BIGINTEGER));
481             map.put(BigDecimal.class, new Integer JavaDoc(BIGDECIMAL));
482             return map;
483         }
484         
485         private static int getClassCode(Number JavaDoc num) throws TemplateException {
486             try {
487                 return ((Integer JavaDoc)classCodes.get(num.getClass())).intValue();
488             }
489             catch(NullPointerException JavaDoc e) {
490                 if(num == null) {
491                     throw new TemplateException("Unknown number type null", Environment.getCurrentEnvironment());
492                 }
493                 throw new TemplateException("Unknown number type " + num.getClass().getName(), Environment.getCurrentEnvironment());
494             }
495         }
496         
497         private static int getCommonClassCode(Number JavaDoc num1, Number JavaDoc num2) throws TemplateException {
498             int c1 = getClassCode(num1);
499             int c2 = getClassCode(num2);
500             int c = c1 > c2 ? c1 : c2;
501             // If BigInteger is combined with a Float or Double, the result is a
502
// BigDecimal instead of BigInteger in order not to lose the
503
// fractional parts. If Float is combined with Long, the result is a
504
// Double instead of Float to preserve the bigger bit width.
505
switch(c) {
506                 case FLOAT: {
507                     if((c1 < c2 ? c1 : c2) == LONG) {
508                         return DOUBLE;
509                     }
510                     break;
511                 }
512                 case BIGINTEGER: {
513                     int min = c1 < c2 ? c1 : c2;
514                     if(min == DOUBLE || min == FLOAT) {
515                         return BIGDECIMAL;
516                     }
517                     break;
518                 }
519             }
520             return c;
521         }
522         
523         private static BigInteger toBigInteger(Number JavaDoc num) {
524             return num instanceof BigInteger ? (BigInteger) num : new BigInteger(num.toString());
525         }
526     }
527
528     private static BigDecimal toBigDecimal(Number JavaDoc num) {
529         return num instanceof BigDecimal ? (BigDecimal) num : new BigDecimal(num.toString());
530     }
531 }
532
Popular Tags