/******************************************************************************* * Copyright © 2011, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * *******************************************************************************/ package org.eclipse.edt.runtime.java.eglx.lang; import java.math.BigDecimal; import java.math.BigInteger; import org.eclipse.edt.javart.AnyBoxedObject; import org.eclipse.edt.javart.Constants; import org.eclipse.edt.javart.messages.Message; import org.eclipse.edt.javart.util.NumericUtil; import eglx.lang.*; /** * Class to be used in processing Decimal operations * @author twilson */ public class EDecimal extends AnyBoxedObject<BigDecimal> implements eglx.lang.ENumber { private static final long serialVersionUID = Constants.SERIAL_VERSION_UID; /** * An array used for the upper limits of digit-oriented items that have values with decimals. */ private static final BigDecimal[][] MAX_DIGIT_ORIENTED_BD_VALUES; /** * An array used for the lower limits of digit-oriented items that have values with decimals. */ private static final BigDecimal[][] MIN_DIGIT_ORIENTED_BD_VALUES; /** * An array used for the upper limits of byte-oriented items that have values with decimals. */ private static final BigDecimal[][] MAX_BYTE_ORIENTED_BD_VALUES; /** * An array used for the lower limits of byte-oriented items that have values with decimals. */ private static final BigDecimal[][] MIN_BYTE_ORIENTED_BD_VALUES; /** */ public static final int TRUNCATE_BD = BigDecimal.ROUND_DOWN; /** */ public static final int ROUND_BD = BigDecimal.ROUND_HALF_UP; public static final int BIGDECIMAL_RESULT_SCALE = 32; // Initialize the BD_VALUES arrays. static { // The first dimension of the DIGIT_ORIENTED arrays is based on the // length of the items (1-32). The second dimension is long enough to // hold the number of decimal digits that the items might have. MAX_DIGIT_ORIENTED_BD_VALUES = new BigDecimal[34][]; MAX_DIGIT_ORIENTED_BD_VALUES[0] = new BigDecimal[1]; MAX_DIGIT_ORIENTED_BD_VALUES[0][0] = BigDecimal.ZERO; MIN_DIGIT_ORIENTED_BD_VALUES = new BigDecimal[34][]; MIN_DIGIT_ORIENTED_BD_VALUES[0] = new BigDecimal[1]; MIN_DIGIT_ORIENTED_BD_VALUES[0][0] = BigDecimal.ZERO; for (int i = 1; i < 34; i++) { MAX_DIGIT_ORIENTED_BD_VALUES[i] = new BigDecimal[i + 1]; MIN_DIGIT_ORIENTED_BD_VALUES[i] = new BigDecimal[i + 1]; } // The first dimension of the BYTE_ORIENTED arrays is based on // the length of the items (4, 9, or 18 digits). The second dimension // is long enough to hold the number of decimal digits that the items // might have. MAX_BYTE_ORIENTED_BD_VALUES = new BigDecimal[3][]; MAX_BYTE_ORIENTED_BD_VALUES[0] = new BigDecimal[5]; MAX_BYTE_ORIENTED_BD_VALUES[0][0] = BigDecimal.ZERO; MAX_BYTE_ORIENTED_BD_VALUES[1] = new BigDecimal[10]; MAX_BYTE_ORIENTED_BD_VALUES[1][0] = BigDecimal.ZERO; MAX_BYTE_ORIENTED_BD_VALUES[2] = new BigDecimal[19]; MAX_BYTE_ORIENTED_BD_VALUES[2][0] = BigDecimal.ZERO; MIN_BYTE_ORIENTED_BD_VALUES = new BigDecimal[3][]; MIN_BYTE_ORIENTED_BD_VALUES[0] = new BigDecimal[5]; MIN_BYTE_ORIENTED_BD_VALUES[0][0] = BigDecimal.ZERO; MIN_BYTE_ORIENTED_BD_VALUES[1] = new BigDecimal[10]; MIN_BYTE_ORIENTED_BD_VALUES[1][0] = BigDecimal.ZERO; MIN_BYTE_ORIENTED_BD_VALUES[2] = new BigDecimal[19]; MIN_BYTE_ORIENTED_BD_VALUES[2][0] = BigDecimal.ZERO; } /** * An array used for the upper limits of digit-oriented items (their length specifies a number of digits not a number of * bytes) without decimals. */ public static final BigInteger[] MAX_DIGIT_ORIENTED_BI_VALUES = { BigInteger.ZERO, BigInteger.valueOf(9), BigInteger.valueOf(99), BigInteger.valueOf(999), BigInteger.valueOf(9999), BigInteger.valueOf(99999), BigInteger.valueOf(999999), BigInteger.valueOf(9999999), BigInteger.valueOf(99999999), BigInteger.valueOf(999999999), BigInteger.valueOf(9999999999L), BigInteger.valueOf(99999999999L), BigInteger.valueOf(999999999999L), BigInteger.valueOf(9999999999999L), BigInteger.valueOf(99999999999999L), BigInteger.valueOf(999999999999999L), BigInteger.valueOf(9999999999999999L), BigInteger.valueOf(99999999999999999L), BigInteger.valueOf(999999999999999999L), new BigInteger("9999999999999999999"), new BigInteger("99999999999999999999"), new BigInteger("999999999999999999999"), new BigInteger("9999999999999999999999"), new BigInteger("99999999999999999999999"), new BigInteger("999999999999999999999999"), new BigInteger("9999999999999999999999999"), new BigInteger("99999999999999999999999999"), new BigInteger("999999999999999999999999999"), new BigInteger("9999999999999999999999999999"), new BigInteger("99999999999999999999999999999"), new BigInteger("999999999999999999999999999999"), new BigInteger("9999999999999999999999999999999"), new BigInteger("99999999999999999999999999999999"), new BigInteger("999999999999999999999999999999999") }; /** * An array used for the lower limits of digit-oriented items (their length specifies a number of digits not a number of * bytes) without decimals. */ public static final BigInteger[] MIN_DIGIT_ORIENTED_BI_VALUES = { BigInteger.ZERO, MAX_DIGIT_ORIENTED_BI_VALUES[1].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[2].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[3].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[4].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[5].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[6].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[7].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[8].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[9].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[10].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[11].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[12].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[13].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[14].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[15].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[16].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[17].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[18].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[19].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[20].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[21].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[22].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[23].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[24].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[25].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[26].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[27].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[28].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[29].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[30].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[31].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[32].negate(), MAX_DIGIT_ORIENTED_BI_VALUES[33].negate() }; private int maxPrecision; private int maxDecimals; public int getPrecision() { return maxPrecision; } public int getDecimals() { return maxDecimals; } private EDecimal(BigDecimal value) { super(value); if (value != null) { maxPrecision = value.precision(); maxDecimals = value.scale(); } } private EDecimal(BigDecimal value, int precision) { super(value); if (value != null) { maxPrecision = precision; } } private EDecimal(BigDecimal value, int precision, int decimals) { super(value); if (value != null) { maxPrecision = precision; maxDecimals = decimals; } } public String toString() { return EString.asString(object); } public static EDecimal ezeBox(BigDecimal value) { return new EDecimal(value); } public static EDecimal ezeBox(BigDecimal value, int precision, int decimals) { return new EDecimal(value, precision, decimals); } /** * Returns the upper limit for a digit-oriented item (its length specifies a number of digits not a number of bytes). * This method will cache values in MAX_DIGIT_ORIENTED_BD_VALUES. * @param length the number of decimal digits stored by the item. * @param decimals the number of decimal digits stored by the item. * @return the largest number that the item can store. */ public static BigDecimal getMaxValue(int length, int decimals) { // Get the cached limit. BigDecimal max = MAX_DIGIT_ORIENTED_BD_VALUES[length][decimals]; if (max == null) { // Need to make it, and save it for later. max = new BigDecimal(MAX_DIGIT_ORIENTED_BI_VALUES[length], decimals); MAX_DIGIT_ORIENTED_BD_VALUES[length][decimals] = max; } return max; } /** * Returns the lower limit for a digit-oriented item (its length specifies a number of digits not a number of bytes). * This method will cache values in MIN_DIGIT_ORIENTED_BD_VALUES. * @param length the number of decimal digits stored by the item. * @param decimals the number of decimal digits stored by the item. * @return the smallest number that the item can store. */ public static BigDecimal getMinValue(int length, int decimals) { // Get the cached limit. BigDecimal min = MIN_DIGIT_ORIENTED_BD_VALUES[length][decimals]; if (min == null) { // Need to make it, and save it for later. BigDecimal max = getMaxValue(length, decimals); min = max.negate(); MIN_DIGIT_ORIENTED_BD_VALUES[length][decimals] = min; } return min; } /** * Call this when the assignment overflows and the target is a numeric type. * @param program * @param target * @param source * @throws AnyException */ private static BigDecimal handleNumericOverflow(BigDecimal value, int precision, int scale, boolean ignoreOverflow) throws AnyException { BigDecimal result = value; if (ignoreOverflow) { // Don't throw an exception. Store as much of the source as possible. // This algorithm comes from v6, and from VAGen before that. // TODO is this still the right algorithm? BigDecimal divisor = new BigDecimal(BigInteger.ONE, -(precision - scale)); // This is result = source % divisor. result = value.subtract(value.divide(divisor, 0, BigDecimal.ROUND_DOWN).multiply(divisor)); } else { // The program wants an exception to be thrown. throw new NumericOverflowException(); // String message = org.eclipse.edt.javart.util.JavartUtil.errorMessage( // program, // Message.EXPRESSION_OVERFLOW, // new Object[]{value.toString() + " as decimal(" + precision + ", " + scale + ")"}); // throw new RuntimeException(Message.EXPRESSION_OVERFLOW, message); } return result; } public static Object ezeCast(Object value, Object[] constraints) throws AnyException { Integer[] args = new Integer[constraints.length]; java.lang.System.arraycopy(constraints, 0, args, 0, args.length); return ezeCast(value, args); } public static BigDecimal ezeCast(Object value, Integer... args) throws AnyException { return (BigDecimal) EAny.ezeCast(value, "asDecimal", EDecimal.class, new Class[] { Integer[].class }, args); } public static boolean ezeIsa(Object value, Integer... args) { boolean isa = (value instanceof EDecimal && ((EDecimal) value).ezeUnbox() != null); if (isa) { if (args.length != 0) { isa = ((EDecimal) value).getPrecision() == args[0]; if (isa && args.length == 1) isa = ((EDecimal) value).getDecimals() == 0; else if (isa && args.length == 2) isa = ((EDecimal) value).getDecimals() == args[1]; } } else { isa = value instanceof BigDecimal; if (isa && args.length != 0) { isa = ((BigDecimal) value).precision() == args[0]; if (isa && args.length == 1) isa = ((BigDecimal) value).scale() == 0; else if (isa && args.length == 2) isa = ((BigDecimal) value).scale() == args[1]; } } return isa; } public static BigDecimal asDecimal(Short value, Integer... args) throws AnyException { if (value == null) return null; if (args.length == 2) return asDecimal(BigDecimal.valueOf(value), args[0], args[1]); else return BigDecimal.valueOf(value); } public static BigDecimal asDecimal(ESmallint value, Integer... args) throws AnyException { if (value == null) return null; if (args.length == 2) return asDecimal(BigDecimal.valueOf(value.ezeUnbox()), args[0], args[1]); else return BigDecimal.valueOf(value.ezeUnbox()); } public static BigDecimal asDecimal(Integer value, Integer... args) throws AnyException { if (value == null) return null; if (args.length == 2) return asDecimal(BigDecimal.valueOf(value), args[0], args[1]); else return BigDecimal.valueOf(value); } public static BigDecimal asDecimal(EInt value, Integer... args) throws AnyException { if (value == null) return null; if (args.length == 2) return asDecimal(BigDecimal.valueOf(value.ezeUnbox()), args[0], args[1]); else return BigDecimal.valueOf(value.ezeUnbox()); } public static BigDecimal asDecimal(Long value, Integer... args) throws AnyException { if (value == null) return null; if (args.length == 2) return asDecimal(BigDecimal.valueOf(value), args[0], args[1]); else return BigDecimal.valueOf(value); } public static BigDecimal asDecimal(EBigint value, Integer... args) throws AnyException { if (value == null) return null; if (args.length == 2) return asDecimal(BigDecimal.valueOf(value.ezeUnbox()), args[0], args[1]); else return BigDecimal.valueOf(value.ezeUnbox()); } public static BigDecimal asDecimal(Float value, Integer... args) throws AnyException { if (value == null) return null; if (args.length == 2) return asDecimal(BigDecimal.valueOf(value), args[0], args[1]); else return BigDecimal.valueOf(value); } public static BigDecimal asDecimal(ESmallfloat value, Integer... args) throws AnyException { if (value == null) return null; if (args.length == 2) return asDecimal(BigDecimal.valueOf(value.ezeUnbox()), args[0], args[1]); else return BigDecimal.valueOf(value.ezeUnbox()); } public static BigDecimal asDecimal(Double value, Integer... args) throws AnyException { if (value == null) return null; if (args.length == 2) return asDecimal(BigDecimal.valueOf(value), args[0], args[1]); else return BigDecimal.valueOf(value); } public static BigDecimal asDecimal(EFloat value, Integer... args) throws AnyException { if (value == null) return null; if (args.length == 2) return asDecimal(BigDecimal.valueOf(value.ezeUnbox()), args[0], args[1]); else return BigDecimal.valueOf(value.ezeUnbox()); } public static BigDecimal asDecimal(BigDecimal value, Integer... args) throws AnyException { if (value == null) return null; if (args.length == 2) return asDecimal(value, args[0], args[1]); else return value; } public static BigDecimal asDecimal(EDecimal value, Integer... args) throws AnyException { if (value == null) return null; if (args.length == 2) return asDecimal(value.ezeUnbox(), args[0], args[1]); else return value.ezeUnbox(); } public static BigDecimal asDecimal(BigDecimal value, int precision, int scale) throws AnyException { if (value == null) return null; return asDecimal(value, getMaxValue(precision, scale), getMinValue(precision, scale), precision, scale, false); } public static BigDecimal asDecimal(EDecimal value, int precision, int scale) throws AnyException { if (value == null) return null; return asDecimal(value.ezeUnbox(), getMaxValue(precision, scale), getMinValue(precision, scale), precision, scale, false); } public static BigDecimal asDecimal(BigDecimal value, BigDecimal max, BigDecimal min, int precision, int scale) throws AnyException { if (value == null || max == null || min == null) //TODO why check max and min? can they ever be null? return null; return asDecimal(value, max, min, precision, scale, false); } public static BigDecimal asDecimal(BigDecimal value, BigDecimal max, BigDecimal min, int precision, int scale, boolean ignoreOverflow) throws AnyException { if (value == null || max == null || min == null) //TODO why check max and min? can they ever be null? return null; BigDecimal result = value; if (scale < value.scale()) { // truncate or round the value based on the program setting // TODO set this variable up in Program // if ( program._truncateDecimals ) // { // result = value.setScale( scale, TRUNCATE_BD ); // } // else // { result = value.setScale(scale, TRUNCATE_BD); // } } if (ignoreOverflow) return result; else { // Now make sure the value isn't too big for the target. if ((result.compareTo(max) <= 0 && result.compareTo(min) >= 0)) return result; else return handleNumericOverflow(value, precision, scale, ignoreOverflow); } } public static BigDecimal asDecimal(BigInteger value, int precision, int scale) throws AnyException { if (value == null) return null; return asDecimal(new BigDecimal(value), getMaxValue(precision, scale), getMinValue(precision, scale), precision, scale, false); } public static BigDecimal asDecimal(Number value, Integer... args) throws AnyException { if (value == null) return null; if (args.length == 2) return asDecimal(BigDecimal.valueOf(value.doubleValue()), args[0], args[1]); else return BigDecimal.valueOf(value.doubleValue()); } public static BigDecimal asDecimal(eglx.lang.ENumber value, Integer... args) throws AnyException { if (value == null) return null; if (args.length == 2) return asDecimal(BigDecimal.valueOf(value.ezeUnbox().doubleValue()), args[0], args[1]); else return BigDecimal.valueOf(value.ezeUnbox().doubleValue()); } public static BigDecimal asDecimal(String value, Integer... args) throws AnyException { if (value == null) return null; try { return asDecimal( new BigDecimal( value ), args ); } catch ( NumberFormatException ex ) { TypeCastException tcx = new TypeCastException(); tcx.actualTypeName = "string"; tcx.castToName = "decimal"; throw tcx.fillInMessage( Message.CONVERSION_ERROR, value, tcx.actualTypeName, tcx.castToName ); } } public static BigDecimal asDecimal(EString value, Integer... args) throws AnyException { if (value == null) return null; return asDecimal(value.ezeUnbox(), args); } public static BigDecimal asDecimal( byte[] value, int precision, int scale ) throws AnyException { if ( value == null ) { return null; } int byteLength = precision / 2 + 1; if ( value.length == byteLength ) { try { return new BigDecimal( NumericUtil.decimalToBigInteger( value, 0, precision ), scale ); } catch ( InvalidArgumentException iax ) { // Ignore it and throw a TypeCastException below. } } TypeCastException tcx = new TypeCastException(); tcx.actualTypeName = "bytes(" + value.length + ')'; tcx.castToName = "decimal(" + precision + ',' + scale + ')'; throw tcx.fillInMessage( Message.CONVERSION_ERROR, value, tcx.actualTypeName, tcx.castToName ); } public static BigDecimal asDecimal( EBytes value, int precision, int scale ) throws AnyException { if ( value == null ) { return null; } return asDecimal( value.ezeUnbox(), precision, scale ); } /** * this is different. Normally we need to place the "as" methods in the corresponding class, but asNumber methods need to * go into the class related to the argument instead */ public static EDecimal asNumber(BigDecimal value, Integer... args) throws AnyException { if (value == null) return null; if (args.length == 2) return EDecimal.ezeBox(value, args[0], args[1]); else return EDecimal.ezeBox(value); } public static EDecimal asNumber(EDecimal value, Integer... args) throws AnyException { if (value == null) return null; return value; } public static BigDecimal negate(BigDecimal op) throws AnyException { if (op == null) { NullValueException nvx = new NullValueException(); throw nvx.fillInMessage( Message.NULL_NOT_ALLOWED ); } return op.negate(); } public static BigDecimal plus(BigDecimal op1, BigDecimal op2) { return op1.add(op2); } public static BigDecimal minus(BigDecimal op1, BigDecimal op2) { return op1.subtract(op2); } public static BigDecimal divide(BigDecimal op1, BigDecimal op2) { return op1.divide(op2, BIGDECIMAL_RESULT_SCALE, TRUNCATE_BD); } public static BigDecimal multiply(BigDecimal op1, BigDecimal op2) { return op1.multiply(op2); } public static BigDecimal remainder(BigDecimal op1, BigDecimal op2) { return op1.remainder(op2); } public static double power(BigDecimal op1, BigDecimal op2) throws AnyException { return StrictMath.pow(op1.doubleValue(), op2.doubleValue()); } public static int compareTo(BigDecimal op1, BigDecimal op2) throws AnyException { return op1.compareTo(op2); } public static boolean equals(BigDecimal op1, BigDecimal op2) { if (op1 == op2) return true; if (op1 == null || op2 == null) return false; return op1.compareTo(op2) == 0; } public static boolean notEquals(BigDecimal op1, BigDecimal op2) { return !equals(op1, op2); } }