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: * Stateless objects used to perform math on the various Number subclasses.
012: * Instances are required so that polymorphic calls work properly, but each
013: * subclass creates a singleton instance to minimize garbage. All methods
014: * must be thread-safe.
015: *
016: * The design goals of this class are as follows:
017: * <ol>
018: * <li>Support a 'least surprising' math model to scripting language users. This
019: * means that exact, or decimal math should be used for default calculations. This
020: * scheme assumes that by default, groovy literals with decimal points are instantiated
021: * as BigDecimal objects rather than binary floating points (Float, Double).
022: * <li>Do not force the appearance of exactness on a number that is by definition not
023: * guaranteed to be exact. In particular this means that if an operand in a NumberMath
024: * operation is a binary floating point number, ensure that the result remains a binary floating point
025: * number (i.e. never automatically promote a binary floating point number to a BigDecimal).
026: * This has the effect of preserving the expectations of binary floating point users and helps performance.
027: * <li>Provide an implementation that is as close as practical to the Java 1.5 BigDecimal math model
028: * which implements precision based floating point decimal math (ANSI X3.274-1996 and
029: * ANSI X3.274-1996/AM 1-2000 (section 7.4).
030: * </ol>
031: *
032: * @author Steve Goetze
033: */
034: public abstract class NumberMath extends Object {
035:
036: public static Number abs(Number number) {
037: return getMath(number).absImpl(number);
038: }
039:
040: public static Number add(Number left, Number right) {
041: return getMath(left, right).addImpl(left, right);
042: }
043:
044: public static Number subtract(Number left, Number right) {
045: return getMath(left, right).subtractImpl(left, right);
046: }
047:
048: public static Number multiply(Number left, Number right) {
049: return getMath(left, right).multiplyImpl(left, right);
050: }
051:
052: public static Number divide(Number left, Number right) {
053: return getMath(left, right).divideImpl(left, right);
054: }
055:
056: public static int compareTo(Number left, Number right) {
057: return getMath(left, right).compareToImpl(left, right);
058: }
059:
060: public static Number or(Number left, Number right) {
061: return getMath(left, right).orImpl(left, right);
062: }
063:
064: public static Number and(Number left, Number right) {
065: return getMath(left, right).andImpl(left, right);
066: }
067:
068: public static Number xor(Number left, Number right) {
069: return getMath(left, right).xorImpl(left, right);
070: }
071:
072: public static Number intdiv(Number left, Number right) {
073: return getMath(left, right).intdivImpl(left, right);
074: }
075:
076: public static Number mod(Number left, Number right) {
077: return getMath(left, right).modImpl(left, right);
078: }
079:
080: /**
081: * For this operation, consider the operands independently. Throw an exception if the right operand
082: * (shift distance) is not an integral type. For the left operand (shift value) also require an integral
083: * type, but do NOT promote from Integer to Long. This is consistent with Java, and makes sense for the
084: * shift operators.
085: */
086: public static Number leftShift(Number left, Number right) {
087: if (isFloatingPoint(right) || isBigDecimal(right)) {
088: throw new UnsupportedOperationException(
089: "Shift distance must be an integral type, but "
090: + right + " (" + right.getClass().getName()
091: + ") was supplied");
092: }
093: return getMath(left).leftShiftImpl(left, right);
094: }
095:
096: /**
097: * For this operation, consider the operands independently. Throw an exception if the right operand
098: * (shift distance) is not an integral type. For the left operand (shift value) also require an integral
099: * type, but do NOT promote from Integer to Long. This is consistent with Java, and makes sense for the
100: * shift operators.
101: */
102: public static Number rightShift(Number left, Number right) {
103: if (isFloatingPoint(right) || isBigDecimal(right)) {
104: throw new UnsupportedOperationException(
105: "Shift distance must be an integral type, but "
106: + right + " (" + right.getClass().getName()
107: + ") was supplied");
108: }
109: return getMath(left).rightShiftImpl(left, right);
110: }
111:
112: /**
113: * For this operation, consider the operands independently. Throw an exception if the right operand
114: * (shift distance) is not an integral type. For the left operand (shift value) also require an integral
115: * type, but do NOT promote from Integer to Long. This is consistent with Java, and makes sense for the
116: * shift operators.
117: */
118: public static Number rightShiftUnsigned(Number left, Number right) {
119: if (isFloatingPoint(right) || isBigDecimal(right)) {
120: throw new UnsupportedOperationException(
121: "Shift distance must be an integral type, but "
122: + right + " (" + right.getClass().getName()
123: + ") was supplied");
124: }
125: return getMath(left).rightShiftUnsignedImpl(left, right);
126: }
127:
128: public static Number negate(Number left) {
129: return getMath(left).negateImpl(left);
130: }
131:
132: public static boolean isFloatingPoint(Number number) {
133: return number instanceof Double || number instanceof Float;
134: }
135:
136: public static boolean isInteger(Number number) {
137: return number instanceof Integer;
138: }
139:
140: public static boolean isLong(Number number) {
141: return number instanceof Long;
142: }
143:
144: public static boolean isBigDecimal(Number number) {
145: return number instanceof BigDecimal;
146: }
147:
148: public static boolean isBigInteger(Number number) {
149: return number instanceof BigInteger;
150: }
151:
152: public static BigDecimal toBigDecimal(Number n) {
153: return (n instanceof BigDecimal ? (BigDecimal) n
154: : new BigDecimal(n.toString()));
155: }
156:
157: public static BigInteger toBigInteger(Number n) {
158: return (n instanceof BigInteger ? (BigInteger) n
159: : new BigInteger(n.toString()));
160: }
161:
162: /**
163: * Determine which NumberMath instance to use, given the supplied operands. This method implements
164: * the type promotion rules discussed in the documentation. Note that by the time this method is
165: * called, any Byte, Character or Short operands will have been promoted to Integer. For reference,
166: * here is the promotion matrix:
167: * bD bI D F L I
168: * bD bD bD D D bD bD
169: * bI bD bI D D bI bI
170: * D D D D D D D
171: * F D D D D D D
172: * L bD bI D D L L
173: * I bD bI D D L I
174: *
175: * Note that for division, if either operand isFloatingPoint, the result will be floating. Otherwise,
176: * the result is BigDecimal
177: */
178: private static NumberMath getMath(Number left, Number right) {
179: if (isFloatingPoint(left) || isFloatingPoint(right)) {
180: return FloatingPointMath.instance;
181: } else if (isBigDecimal(left) || isBigDecimal(right)) {
182: return BigDecimalMath.instance;
183: } else if (isBigInteger(left) || isBigInteger(right)) {
184: return BigIntegerMath.instance;
185: } else if (isLong(left) || isLong(right)) {
186: return LongMath.instance;
187: }
188: return IntegerMath.instance;
189: }
190:
191: private static NumberMath getMath(Number number) {
192: if (isInteger(number)) {
193: return IntegerMath.instance;
194: } else if (isLong(number)) {
195: return LongMath.instance;
196: } else if (isFloatingPoint(number)) {
197: return FloatingPointMath.instance;
198: } else if (isBigDecimal(number)) {
199: return BigDecimalMath.instance;
200: } else if (isBigInteger(number)) {
201: return BigIntegerMath.instance;
202: } else {
203: throw new IllegalArgumentException(
204: "An unexpected Number subclass was supplied.");
205: }
206: }
207:
208: //Subclasses implement according to the type promotion hierarchy rules
209: protected abstract Number absImpl(Number number);
210:
211: protected abstract Number addImpl(Number left, Number right);
212:
213: protected abstract Number subtractImpl(Number left, Number right);
214:
215: protected abstract Number multiplyImpl(Number left, Number right);
216:
217: protected abstract Number divideImpl(Number left, Number right);
218:
219: protected abstract int compareToImpl(Number left, Number right);
220:
221: protected abstract Number negateImpl(Number left);
222:
223: protected Number orImpl(Number left, Number right) {
224: throw createUnsupportedException("or()", left);
225: }
226:
227: protected Number andImpl(Number left, Number right) {
228: throw createUnsupportedException("and()", left);
229: }
230:
231: protected Number xorImpl(Number left, Number right) {
232: throw createUnsupportedException("xor()", left);
233: }
234:
235: protected Number modImpl(Number left, Number right) {
236: throw createUnsupportedException("mod()", left);
237: }
238:
239: protected Number intdivImpl(Number left, Number right) {
240: throw createUnsupportedException("intdiv()", left);
241: }
242:
243: protected Number leftShiftImpl(Number left, Number right) {
244: throw createUnsupportedException("leftShift()", left);
245: }
246:
247: protected Number rightShiftImpl(Number left, Number right) {
248: throw createUnsupportedException("rightShift()", left);
249: }
250:
251: protected Number rightShiftUnsignedImpl(Number left, Number right) {
252: throw createUnsupportedException("rightShiftUnsigned()", left);
253: }
254:
255: protected UnsupportedOperationException createUnsupportedException(
256: String operation, Number left) {
257: return new UnsupportedOperationException("Cannot use "
258: + operation + " on this number type: "
259: + left.getClass().getName() + " with value: " + left);
260: }
261: }
|