View Javadoc

    1   /*
    2    * Created on Mar 7, 2004
    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 intdiv(Number left, Number right) {
    69  		return getMath(left,right).intdivImpl(left,right);
    70   	}
    71
    72  	public static Number mod(Number left, Number right) {
    73          return getMath(left,right).modImpl(left, right);
    74      }
    75
    76      /***
    77       * For this operation, consider the operands independently.  Throw an exception if the right operand
    78       * (shift distance) is not an integral type.  For the left operand (shift value) also require an integral
    79       * type, but do NOT promote from Integer to Long.  This is consistent with Java, and makes sense for the
    80       * shift operators.
    81       */
    82      public static Number leftShift(Number left, Number right) {
    83  		if (isFloatingPoint(right) || isBigDecimal(right)) {
    84  	        throw new UnsupportedOperationException("Shift distance must be an integral type, but " +  right + " (" + right.getClass().getName() + ") was supplied");
    85  		}
    86      	return getMath(left).leftShiftImpl(left,right);
    87      }
    88
    89      /***
    90       * For this operation, consider the operands independently.  Throw an exception if the right operand
    91       * (shift distance) is not an integral type.  For the left operand (shift value) also require an integral
    92       * type, but do NOT promote from Integer to Long.  This is consistent with Java, and makes sense for the
    93       * shift operators.
    94       */
    95      public static Number rightShift(Number left, Number right) {
    96  		if (isFloatingPoint(right) || isBigDecimal(right)) {
    97  	        throw new UnsupportedOperationException("Shift distance must be an integral type, but " +  right + " (" + right.getClass().getName() + ") was supplied");
    98  		}
    99      	return getMath(left).rightShiftImpl(left,right);
    100     }
    101
    102     /***
    103      * For this operation, consider the operands independently.  Throw an exception if the right operand
    104      * (shift distance) is not an integral type.  For the left operand (shift value) also require an integral
    105      * type, but do NOT promote from Integer to Long.  This is consistent with Java, and makes sense for the
    106      * shift operators.
    107      */
    108     public static Number rightShiftUnsigned(Number left, Number right) {
    109 		if (isFloatingPoint(right) || isBigDecimal(right)) {
    110 	        throw new UnsupportedOperationException("Shift distance must be an integral type, but " +  right + " (" + right.getClass().getName() + ") was supplied");
    111 		}
    112     	return getMath(left).rightShiftUnsignedImpl(left,right);
    113     }
    114
    115     public static Number negate(Number left) {
    116         return getMath(left).negateImpl(left);
    117     }
    118
    119     public static boolean isFloatingPoint(Number number) {
    120 		return number instanceof Double || number instanceof Float;
    121 	}
    122
    123 	public static boolean isInteger(Number number) {
    124 		return number instanceof Integer;
    125 	}
    126
    127 	public static boolean isLong(Number number) {
    128 		return number instanceof Long;
    129 	}
    130
    131 	public static boolean isBigDecimal(Number number) {
    132 		return number instanceof BigDecimal;
    133 	}
    134
    135 	public static boolean isBigInteger(Number number) {
    136 		return number instanceof BigInteger;
    137 	}
    138
    139 	public static BigDecimal toBigDecimal(Number n) {
    140 		return (n instanceof BigDecimal ? (BigDecimal) n : new BigDecimal(n.toString()));
    141 	}
    142
    143 	public static BigInteger toBigInteger(Number n) {
    144 		return (n instanceof BigInteger ? (BigInteger) n : new BigInteger(n.toString()));
    145 	}
    146
    147 	/***
    148 	 * Determine which NumberMath instance to use, given the supplied operands.  This method implements
    149 	 * the type promotion rules discussed in the documentation.  Note that by the time this method is
    150 	 * called, any Byte, Character or Short operands will have been promoted to Integer.  For reference,
    151 	 * here is the promotion matrix:
    152 	 *    bD bI  D  F  L  I
    153 	 * bD bD bD  D  D bD bD
    154 	 * bI bD bI  D  D bI bI
    155 	 *  D  D  D  D  D  D  D
    156 	 *  F  D  D  D  D  D  D
    157 	 *  L bD bI  D  D  L  L
    158 	 *  I bD bI  D  D  L  I
    159 	 *
    160 	 * Note that for division, if either operand isFloatingPoint, the result will be floating.  Otherwise,
    161 	 * the result is BigDecimal
    162 	 */
    163 	private static NumberMath getMath(Number left, Number right) {
    164 		if (isFloatingPoint(left) || isFloatingPoint(right)) {
    165 			return FloatingPointMath.instance;
    166 		}
    167 		else if (isBigDecimal(left) || isBigDecimal(right)) {
    168 			return BigDecimalMath.instance;
    169 		}
    170 		else if (isBigInteger(left) || isBigInteger(right)) {
    171 			return BigIntegerMath.instance;
    172 		}
    173 		else if (isLong(left) || isLong(right)){
    174 			return LongMath.instance;
    175 		}
    176 		return IntegerMath.instance;
    177 	}
    178
    179 	private static NumberMath getMath(Number number) {
    180 		if (isInteger(number)) {
    181 			return IntegerMath.instance;
    182 		}
    183 		else if (isLong(number)) {
    184 			return LongMath.instance;
    185 		}
    186 		else if (isFloatingPoint(number)) {
    187 			return FloatingPointMath.instance;
    188 		}
    189 		else if (isBigDecimal(number)) {
    190 			return BigDecimalMath.instance;
    191 		}
    192 		else if (isBigInteger(number)) {
    193 			return BigIntegerMath.instance;
    194 		}
    195 		else {
    196 			throw new IllegalArgumentException("An unexpected Number subclass was supplied.");
    197 		}
    198 	}
    199
    200 	//Subclasses implement according to the type promotion hierarchy rules
    201 	protected abstract Number absImpl(Number number);
    202 	protected abstract Number addImpl(Number left, Number right);
    203 	protected abstract Number subtractImpl(Number left, Number right);
    204 	protected abstract Number multiplyImpl(Number left, Number right);
    205 	protected abstract Number divideImpl(Number left, Number right);
    206 	protected abstract int compareToImpl(Number left, Number right);
    207     protected abstract Number negateImpl(Number left);
    208
    209
    210     protected Number andImpl(Number left, Number right) {
    211         throw createUnsupportedException("and()", left);
    212     }
    213
    214     protected Number modImpl(Number left, Number right) {
    215         throw createUnsupportedException("mod()", left);
    216     }
    217
    218     protected Number intdivImpl(Number left, Number right) {
    219         throw createUnsupportedException("intdiv()", left);
    220     }
    221
    222     protected Number orImpl(Number left, Number right) {
    223         throw createUnsupportedException("or()", left);
    224     }
    225
    226     protected Number leftShiftImpl(Number left, Number right) {
    227         throw createUnsupportedException("leftShift()", left);
    228     }
    229
    230     protected Number rightShiftImpl(Number left, Number right) {
    231         throw createUnsupportedException("rightShift()", left);
    232     }
    233
    234     protected Number rightShiftUnsignedImpl(Number left, Number right) {
    235         throw createUnsupportedException("rightShiftUnsigned()", left);
    236     }
    237
    238     protected UnsupportedOperationException createUnsupportedException(String operation, Number left) {
    239         return new UnsupportedOperationException("Cannot use " + operation + " on this number type: " + left.getClass().getName() + " with value: " + left);
    240     }
    241 }