1
2
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
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 }