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 }