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 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 	//Subclasses implement according to the type promotion hierarchy rules
    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 }