| /* |
| * Copyright 2003-2007 the original author or authors. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.codehaus.groovy.runtime.typehandling; |
| |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| |
| |
| /** |
| * Stateless objects used to perform math on the various Number subclasses. |
| * Instances are required so that polymorphic calls work properly, but each |
| * subclass creates a singleton instance to minimize garbage. All methods |
| * must be thread-safe. |
| * |
| * The design goals of this class are as follows: |
| * <ol> |
| * <li>Support a 'least surprising' math model to scripting language users. This |
| * means that exact, or decimal math should be used for default calculations. This |
| * scheme assumes that by default, groovy literals with decimal points are instantiated |
| * as BigDecimal objects rather than binary floating points (Float, Double). |
| * <li>Do not force the appearance of exactness on a number that is by definition not |
| * guaranteed to be exact. In particular this means that if an operand in a NumberMath |
| * operation is a binary floating point number, ensure that the result remains a binary floating point |
| * number (i.e. never automatically promote a binary floating point number to a BigDecimal). |
| * This has the effect of preserving the expectations of binary floating point users and helps performance. |
| * <li>Provide an implementation that is as close as practical to the Java 1.5 BigDecimal math model |
| * which implements precision based floating point decimal math (ANSI X3.274-1996 and |
| * ANSI X3.274-1996/AM 1-2000 (section 7.4). |
| * </ol> |
| * |
| * @author Steve Goetze |
| */ |
| public abstract class NumberMath { |
| |
| public static Number abs(Number number) { |
| return getMath(number).absImpl(number); |
| } |
| |
| public static Number add(Number left, Number right) { |
| return getMath(left, right).addImpl(left,right); |
| } |
| |
| public static Number subtract(Number left, Number right) { |
| return getMath(left,right).subtractImpl(left,right); |
| } |
| |
| public static Number multiply(Number left, Number right) { |
| return getMath(left,right).multiplyImpl(left,right); |
| } |
| |
| public static Number divide(Number left, Number right) { |
| return getMath(left,right).divideImpl(left,right); |
| } |
| |
| public static int compareTo(Number left, Number right) { |
| return getMath(left,right).compareToImpl(left, right); |
| } |
| |
| public static Number or(Number left, Number right) { |
| return getMath(left,right).orImpl(left, right); |
| } |
| |
| public static Number and(Number left, Number right) { |
| return getMath(left,right).andImpl(left, right); |
| } |
| |
| public static Number xor(Number left, Number right) { |
| return getMath(left,right).xorImpl(left, right); |
| } |
| |
| public static Number intdiv(Number left, Number right) { |
| return getMath(left,right).intdivImpl(left,right); |
| } |
| |
| public static Number mod(Number left, Number right) { |
| return getMath(left,right).modImpl(left, right); |
| } |
| |
| /** |
| * For this operation, consider the operands independently. Throw an exception if the right operand |
| * (shift distance) is not an integral type. For the left operand (shift value) also require an integral |
| * type, but do NOT promote from Integer to Long. This is consistent with Java, and makes sense for the |
| * shift operators. |
| */ |
| public static Number leftShift(Number left, Number right) { |
| if (isFloatingPoint(right) || isBigDecimal(right)) { |
| throw new UnsupportedOperationException("Shift distance must be an integral type, but " + right + " (" + right.getClass().getName() + ") was supplied"); |
| } |
| return getMath(left).leftShiftImpl(left,right); |
| } |
| |
| /** |
| * For this operation, consider the operands independently. Throw an exception if the right operand |
| * (shift distance) is not an integral type. For the left operand (shift value) also require an integral |
| * type, but do NOT promote from Integer to Long. This is consistent with Java, and makes sense for the |
| * shift operators. |
| */ |
| public static Number rightShift(Number left, Number right) { |
| if (isFloatingPoint(right) || isBigDecimal(right)) { |
| throw new UnsupportedOperationException("Shift distance must be an integral type, but " + right + " (" + right.getClass().getName() + ") was supplied"); |
| } |
| return getMath(left).rightShiftImpl(left,right); |
| } |
| |
| /** |
| * For this operation, consider the operands independently. Throw an exception if the right operand |
| * (shift distance) is not an integral type. For the left operand (shift value) also require an integral |
| * type, but do NOT promote from Integer to Long. This is consistent with Java, and makes sense for the |
| * shift operators. |
| */ |
| public static Number rightShiftUnsigned(Number left, Number right) { |
| if (isFloatingPoint(right) || isBigDecimal(right)) { |
| throw new UnsupportedOperationException("Shift distance must be an integral type, but " + right + " (" + right.getClass().getName() + ") was supplied"); |
| } |
| return getMath(left).rightShiftUnsignedImpl(left,right); |
| } |
| |
| public static Number negate(Number left) { |
| return getMath(left).negateImpl(left); |
| } |
| |
| public static boolean isFloatingPoint(Number number) { |
| return number instanceof Double || number instanceof Float; |
| } |
| |
| public static boolean isInteger(Number number) { |
| return number instanceof Integer; |
| } |
| |
| public static boolean isLong(Number number) { |
| return number instanceof Long; |
| } |
| |
| public static boolean isBigDecimal(Number number) { |
| return number instanceof BigDecimal; |
| } |
| |
| public static boolean isBigInteger(Number number) { |
| return number instanceof BigInteger; |
| } |
| |
| public static BigDecimal toBigDecimal(Number n) { |
| return (n instanceof BigDecimal ? (BigDecimal) n : new BigDecimal(n.toString())); |
| } |
| |
| public static BigInteger toBigInteger(Number n) { |
| return (n instanceof BigInteger ? (BigInteger) n : new BigInteger(n.toString())); |
| } |
| |
| /** |
| * Determine which NumberMath instance to use, given the supplied operands. This method implements |
| * the type promotion rules discussed in the documentation. Note that by the time this method is |
| * called, any Byte, Character or Short operands will have been promoted to Integer. For reference, |
| * here is the promotion matrix: |
| * bD bI D F L I |
| * bD bD bD D D bD bD |
| * bI bD bI D D bI bI |
| * D D D D D D D |
| * F D D D D D D |
| * L bD bI D D L L |
| * I bD bI D D L I |
| * |
| * Note that for division, if either operand isFloatingPoint, the result will be floating. Otherwise, |
| * the result is BigDecimal |
| */ |
| private static NumberMath getMath(Number left, Number right) { |
| if (isFloatingPoint(left) || isFloatingPoint(right)) { |
| return FloatingPointMath.INSTANCE; |
| } |
| else if (isBigDecimal(left) || isBigDecimal(right)) { |
| return BigDecimalMath.INSTANCE; |
| } |
| else if (isBigInteger(left) || isBigInteger(right)) { |
| return BigIntegerMath.INSTANCE; |
| } |
| else if (isLong(left) || isLong(right)){ |
| return LongMath.INSTANCE; |
| } |
| return IntegerMath.INSTANCE; |
| } |
| |
| private static NumberMath getMath(Number number) { |
| if (isInteger(number)) { |
| return IntegerMath.INSTANCE; |
| } |
| else if (isLong(number)) { |
| return LongMath.INSTANCE; |
| } |
| else if (isFloatingPoint(number)) { |
| return FloatingPointMath.INSTANCE; |
| } |
| else if (isBigDecimal(number)) { |
| return BigDecimalMath.INSTANCE; |
| } |
| else if (isBigInteger(number)) { |
| return BigIntegerMath.INSTANCE; |
| } |
| else { |
| throw new IllegalArgumentException("An unexpected Number subclass was supplied."); |
| } |
| } |
| |
| //Subclasses implement according to the type promotion hierarchy rules |
| protected abstract Number absImpl(Number number); |
| protected abstract Number addImpl(Number left, Number right); |
| protected abstract Number subtractImpl(Number left, Number right); |
| protected abstract Number multiplyImpl(Number left, Number right); |
| protected abstract Number divideImpl(Number left, Number right); |
| protected abstract int compareToImpl(Number left, Number right); |
| protected abstract Number negateImpl(Number left); |
| |
| |
| protected Number orImpl(Number left, Number right) { |
| throw createUnsupportedException("or()", left); |
| } |
| |
| protected Number andImpl(Number left, Number right) { |
| throw createUnsupportedException("and()", left); |
| } |
| |
| protected Number xorImpl(Number left, Number right) { |
| throw createUnsupportedException("xor()", left); |
| } |
| |
| protected Number modImpl(Number left, Number right) { |
| throw createUnsupportedException("mod()", left); |
| } |
| |
| protected Number intdivImpl(Number left, Number right) { |
| throw createUnsupportedException("intdiv()", left); |
| } |
| |
| protected Number leftShiftImpl(Number left, Number right) { |
| throw createUnsupportedException("leftShift()", left); |
| } |
| |
| protected Number rightShiftImpl(Number left, Number right) { |
| throw createUnsupportedException("rightShift()", left); |
| } |
| |
| protected Number rightShiftUnsignedImpl(Number left, Number right) { |
| throw createUnsupportedException("rightShiftUnsigned()", left); |
| } |
| |
| protected UnsupportedOperationException createUnsupportedException(String operation, Number left) { |
| return new UnsupportedOperationException("Cannot use " + operation + " on this number type: " + left.getClass().getName() + " with value: " + left); |
| } |
| } |