001 /*
002 * Created on Mar 7, 2004
003 *
004 */
005 package org.codehaus.groovy.runtime.typehandling;
006
007 import java.math.BigDecimal;
008 import java.math.BigInteger;
009
010
011 /**
012 * Stateless objects used to perform math on the various Number subclasses.
013 * Instances are required so that polymorphic calls work properly, but each
014 * subclass creates a singleton instance to minimize garbage. All methods
015 * must be thread-safe.
016 *
017 * The design goals of this class are as follows:
018 * <ol>
019 * <li>Support a 'least surprising' math model to scripting language users. This
020 * means that exact, or decimal math should be used for default calculations. This
021 * scheme assumes that by default, groovy literals with decimal points are instantiated
022 * as BigDecimal objects rather than binary floating points (Float, Double).
023 * <li>Do not force the appearance of exactness on a number that is by definition not
024 * guaranteed to be exact. In particular this means that if an operand in a NumberMath
025 * operation is a binary floating point number, ensure that the result remains a binary floating point
026 * number (i.e. never automatically promote a binary floating point number to a BigDecimal).
027 * This has the effect of preserving the expectations of binary floating point users and helps performance.
028 * <li>Provide an implementation that is as close as practical to the Java 1.5 BigDecimal math model
029 * which implements precision based floating point decimal math (ANSI X3.274-1996 and
030 * ANSI X3.274-1996/AM 1-2000 (section 7.4).
031 * </ol>
032 *
033 * @author Steve Goetze
034 */
035 public abstract class NumberMath extends Object {
036
037 public static Number abs(Number number) {
038 return getMath(number).absImpl(number);
039 }
040
041 public static Number add(Number left, Number right) {
042 return getMath(left, right).addImpl(left,right);
043 }
044
045 public static Number subtract(Number left, Number right) {
046 return getMath(left,right).subtractImpl(left,right);
047 }
048
049 public static Number multiply(Number left, Number right) {
050 return getMath(left,right).multiplyImpl(left,right);
051 }
052
053 public static Number divide(Number left, Number right) {
054 return getMath(left,right).divideImpl(left,right);
055 }
056
057 public static int compareTo(Number left, Number right) {
058 return getMath(left,right).compareToImpl(left, right);
059 }
060
061 public static Number or(Number left, Number right) {
062 return getMath(left,right).orImpl(left, right);
063 }
064
065 public static Number and(Number left, Number right) {
066 return getMath(left,right).andImpl(left, right);
067 }
068
069 public static Number xor(Number left, Number right) {
070 return getMath(left,right).xorImpl(left, right);
071 }
072
073 public static Number intdiv(Number left, Number right) {
074 return getMath(left,right).intdivImpl(left,right);
075 }
076
077 public static Number mod(Number left, Number right) {
078 return getMath(left,right).modImpl(left, right);
079 }
080
081 /**
082 * For this operation, consider the operands independently. Throw an exception if the right operand
083 * (shift distance) is not an integral type. For the left operand (shift value) also require an integral
084 * type, but do NOT promote from Integer to Long. This is consistent with Java, and makes sense for the
085 * shift operators.
086 */
087 public static Number leftShift(Number left, Number right) {
088 if (isFloatingPoint(right) || isBigDecimal(right)) {
089 throw new UnsupportedOperationException("Shift distance must be an integral type, but " + right + " (" + right.getClass().getName() + ") was supplied");
090 }
091 return getMath(left).leftShiftImpl(left,right);
092 }
093
094 /**
095 * For this operation, consider the operands independently. Throw an exception if the right operand
096 * (shift distance) is not an integral type. For the left operand (shift value) also require an integral
097 * type, but do NOT promote from Integer to Long. This is consistent with Java, and makes sense for the
098 * shift operators.
099 */
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 }