View Javadoc

1   /*
2    * Created on Mar 7, 2004
3    *
4    */
5   package org.codehaus.groovy.runtime;
6   
7   import java.math.BigDecimal;
8   import java.math.BigInteger;
9   
10  /***
11   * Stateless objects used to perform math on the various Number subclasses.
12   * Instances are required so that polymorphic calls work properly, but each
13   * subclass creates a singleton instance to minimize garbage.  All methods
14   * must be thread-safe.
15   * 
16   * The design goals of this class are as follows:
17   * <ol>
18   * <li>Support a 'least surprising' math model to scripting language users.  This
19   * means that exact, or decimal math should be used for default calculations.  This
20   * scheme assumes that by default, groovy literals with decimal points are instantiated
21   * as BigDecimal objects rather than binary floating points (Float, Double). 
22   * <li>Do not force the appearance of exactness on a number that is by definition not 
23   * guaranteed to be exact.  In particular this means that if an operand in a NumberMath 
24   * operation is a binary floating point number, ensure that the result remains a binary floating point 
25   * number (i.e. never automatically promote a binary floating point number to a BigDecimal).  
26   * This has the effect of preserving the expectations of binary floating point users and helps performance.
27   * <li>Provide an implementation that is as close as practical to the Java 1.5 BigDecimal math model 
28   * which implements precision based floating point decimal math (ANSI X3.274-1996 and 
29   * ANSI X3.274-1996/AM 1-2000 (section 7.4).  
30   * </ol>
31   * 
32   * @author Steve Goetze
33   */
34  public abstract class NumberMath extends Object {
35  		
36  	public static Number abs(Number number) {
37  		return getMath(number).absImpl(number);
38  	}
39  	
40  	public static Number add(Number left, Number right) {
41  		return getMath(left, right).addImpl(left,right);
42  	}
43  	
44  	public static Number subtract(Number left, Number right) {
45  		return getMath(left,right).subtractImpl(left,right);
46  	}
47  	
48  	public static Number multiply(Number left, Number right) {
49  		return getMath(left,right).multiplyImpl(left,right);
50  	}
51  	
52  	public static Number divide(Number left, Number right) {
53  		return getMath(left,right).divideImpl(left,right);
54   	}
55   	 
56  	public static int compareTo(Number left, Number right) {
57  		return getMath(left,right).compareToImpl(left, right);
58  	}
59  	
60      public static Number or(Number left, Number right) {
61          return getMath(left,right).orImpl(left, right);
62      }
63      
64      public static Number and(Number left, Number right) {
65          return getMath(left,right).andImpl(left, right);
66      }
67      
68      public static Number xor(Number left, Number right) {
69          return getMath(left,right).xorImpl(left, right);
70      }
71      
72  	public static Number intdiv(Number left, Number right) {
73  		return getMath(left,right).intdivImpl(left,right);
74   	}
75  
76  	public static Number mod(Number left, Number right) {
77          return getMath(left,right).modImpl(left, right);
78      }
79  
80      /***
81       * For this operation, consider the operands independently.  Throw an exception if the right operand
82       * (shift distance) is not an integral type.  For the left operand (shift value) also require an integral
83       * type, but do NOT promote from Integer to Long.  This is consistent with Java, and makes sense for the
84       * shift operators.
85       */
86      public static Number leftShift(Number left, Number right) {
87  		if (isFloatingPoint(right) || isBigDecimal(right)) {
88  	        throw new UnsupportedOperationException("Shift distance must be an integral type, but " +  right + " (" + right.getClass().getName() + ") was supplied");
89  		}
90      	return getMath(left).leftShiftImpl(left,right);
91      }
92      
93      /***
94       * For this operation, consider the operands independently.  Throw an exception if the right operand
95       * (shift distance) is not an integral type.  For the left operand (shift value) also require an integral
96       * type, but do NOT promote from Integer to Long.  This is consistent with Java, and makes sense for the
97       * shift operators.
98       */
99      public static Number rightShift(Number left, Number right) {
100 		if (isFloatingPoint(right) || isBigDecimal(right)) {
101 	        throw new UnsupportedOperationException("Shift distance must be an integral type, but " +  right + " (" + right.getClass().getName() + ") was supplied");
102 		}
103     	return getMath(left).rightShiftImpl(left,right);
104     }
105     
106     /***
107      * For this operation, consider the operands independently.  Throw an exception if the right operand
108      * (shift distance) is not an integral type.  For the left operand (shift value) also require an integral
109      * type, but do NOT promote from Integer to Long.  This is consistent with Java, and makes sense for the
110      * shift operators.
111      */
112     public static Number rightShiftUnsigned(Number left, Number right) {
113 		if (isFloatingPoint(right) || isBigDecimal(right)) {
114 	        throw new UnsupportedOperationException("Shift distance must be an integral type, but " +  right + " (" + right.getClass().getName() + ") was supplied");
115 		}
116     	return getMath(left).rightShiftUnsignedImpl(left,right);
117     }
118     
119     public static Number negate(Number left) {
120         return getMath(left).negateImpl(left);
121     }
122     
123     public static boolean isFloatingPoint(Number number) {
124 		return number instanceof Double || number instanceof Float;
125 	}
126 
127 	public static boolean isInteger(Number number) {
128 		return number instanceof Integer;
129 	}
130 
131 	public static boolean isLong(Number number) {
132 		return number instanceof Long;
133 	}
134 
135 	public static boolean isBigDecimal(Number number) {
136 		return number instanceof BigDecimal;
137 	}
138 
139 	public static boolean isBigInteger(Number number) {
140 		return number instanceof BigInteger;
141 	}
142 
143 	public static BigDecimal toBigDecimal(Number n) {
144 		return (n instanceof BigDecimal ? (BigDecimal) n : new BigDecimal(n.toString()));
145 	}
146 				
147 	public static BigInteger toBigInteger(Number n) {
148 		return (n instanceof BigInteger ? (BigInteger) n : new BigInteger(n.toString()));
149 	}
150 					
151 	/***
152 	 * Determine which NumberMath instance to use, given the supplied operands.  This method implements
153 	 * the type promotion rules discussed in the documentation.  Note that by the time this method is
154 	 * called, any Byte, Character or Short operands will have been promoted to Integer.  For reference,
155 	 * here is the promotion matrix:
156 	 *    bD bI  D  F  L  I
157 	 * bD bD bD  D  D bD bD
158 	 * bI bD bI  D  D bI bI
159 	 *  D  D  D  D  D  D  D
160 	 *  F  D  D  D  D  D  D
161 	 *  L bD bI  D  D  L  L
162 	 *  I bD bI  D  D  L  I
163 	 * 
164 	 * Note that for division, if either operand isFloatingPoint, the result will be floating.  Otherwise,
165 	 * the result is BigDecimal
166 	 */
167 	private static NumberMath getMath(Number left, Number right) {
168 		if (isFloatingPoint(left) || isFloatingPoint(right)) {
169 			return FloatingPointMath.instance;
170 		}
171 		else if (isBigDecimal(left) || isBigDecimal(right)) {
172 			return BigDecimalMath.instance;
173 		}
174 		else if (isBigInteger(left) || isBigInteger(right)) {
175 			return BigIntegerMath.instance;
176 		}
177 		else if (isLong(left) || isLong(right)){
178 			return LongMath.instance;
179 		}
180 		return IntegerMath.instance;
181 	}
182 
183 	private static NumberMath getMath(Number number) {
184 		if (isInteger(number)) {
185 			return IntegerMath.instance;
186 		}
187 		else if (isLong(number)) {
188 			return LongMath.instance;
189 		}
190 		else if (isFloatingPoint(number)) {
191 			return FloatingPointMath.instance;
192 		}			
193 		else if (isBigDecimal(number)) {
194 			return BigDecimalMath.instance;
195 		}
196 		else if (isBigInteger(number)) {
197 			return BigIntegerMath.instance;
198 		}
199 		else {
200 			throw new IllegalArgumentException("An unexpected Number subclass was supplied.");
201 		}
202 	}
203 	
204 	//Subclasses implement according to the type promotion hierarchy rules
205 	protected abstract Number absImpl(Number number);
206 	protected abstract Number addImpl(Number left, Number right);
207 	protected abstract Number subtractImpl(Number left, Number right);
208 	protected abstract Number multiplyImpl(Number left, Number right);
209 	protected abstract Number divideImpl(Number left, Number right);
210 	protected abstract int compareToImpl(Number left, Number right);
211     protected abstract Number negateImpl(Number left);
212 
213 
214     protected Number orImpl(Number left, Number right) {
215         throw createUnsupportedException("or()", left);
216     }
217     
218     protected Number andImpl(Number left, Number right) {
219         throw createUnsupportedException("and()", left);
220     }
221 
222     protected Number xorImpl(Number left, Number right) {
223         throw createUnsupportedException("xor()", left);
224     }
225     
226     protected Number modImpl(Number left, Number right) {
227         throw createUnsupportedException("mod()", left);
228     }
229     
230     protected Number intdivImpl(Number left, Number right) {
231         throw createUnsupportedException("intdiv()", left);
232     }
233     
234     protected Number leftShiftImpl(Number left, Number right) {
235         throw createUnsupportedException("leftShift()", left);
236     }
237 
238     protected Number rightShiftImpl(Number left, Number right) {
239         throw createUnsupportedException("rightShift()", left);
240     }
241 
242     protected Number rightShiftUnsignedImpl(Number left, Number right) {
243         throw createUnsupportedException("rightShiftUnsigned()", left);
244     }
245 
246     protected UnsupportedOperationException createUnsupportedException(String operation, Number left) {
247         return new UnsupportedOperationException("Cannot use " + operation + " on this number type: " + left.getClass().getName() + " with value: " + left);
248     }
249 }