View Javadoc

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