0001: /*
0002:
0003: Derby - Class org.apache.derby.iapi.types.SQLDecimal
0004:
0005: Licensed to the Apache Software Foundation (ASF) under one or more
0006: contributor license agreements. See the NOTICE file distributed with
0007: this work for additional information regarding copyright ownership.
0008: The ASF licenses this file to you under the Apache License, Version 2.0
0009: (the "License"); you may not use this file except in compliance with
0010: the License. You may obtain a copy of the License at
0011:
0012: http://www.apache.org/licenses/LICENSE-2.0
0013:
0014: Unless required by applicable law or agreed to in writing, software
0015: distributed under the License is distributed on an "AS IS" BASIS,
0016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017: See the License for the specific language governing permissions and
0018: limitations under the License.
0019:
0020: */
0021:
0022: package org.apache.derby.iapi.types;
0023:
0024: import org.apache.derby.iapi.reference.SQLState;
0025:
0026: import org.apache.derby.iapi.services.io.ArrayInputStream;
0027:
0028: import org.apache.derby.iapi.services.sanity.SanityManager;
0029:
0030: import org.apache.derby.iapi.services.io.StoredFormatIds;
0031: import org.apache.derby.iapi.services.io.Storable;
0032:
0033: import org.apache.derby.iapi.error.StandardException;
0034:
0035: import org.apache.derby.iapi.services.cache.ClassSize;
0036: import org.apache.derby.iapi.services.info.JVMInfo;
0037:
0038: import java.math.BigDecimal;
0039: import java.math.BigInteger;
0040: import java.lang.Math;
0041: import java.lang.reflect.Method;
0042: import java.lang.reflect.InvocationTargetException;
0043: import java.io.ObjectOutput;
0044: import java.io.ObjectInput;
0045: import java.io.IOException;
0046:
0047: import java.sql.ResultSet;
0048: import java.sql.PreparedStatement;
0049: import java.sql.SQLException;
0050: import java.sql.Types;
0051:
0052: /**
0053: * SQLDecimal satisfies the DataValueDescriptor
0054: * interfaces (i.e., OrderableDataType). It implements a numeric/decimal column,
0055: * e.g. for * storing a column value; it can be specified
0056: * when constructed to not allow nulls. Nullability cannot be changed
0057: * after construction, as it affects the storage size and mechanism.
0058: * <p>
0059: * Because OrderableDataType is a subtype of DataType,
0060: * SQLDecimal can play a role in either a DataType/Row
0061: * or a OrderableDataType/Row, interchangeably.
0062: * <p>
0063: * We assume the store has a flag for nullness of the value,
0064: * and simply return a 0-length array for the stored form
0065: * when the value is null.
0066: *
0067: * @author jamie
0068: */
0069: public final class SQLDecimal extends NumberDataType implements
0070: VariableSizeDataValue {
0071: private static final BigDecimal ZERO = BigDecimal.valueOf(0L);
0072: private static final BigDecimal ONE = BigDecimal.valueOf(1L);
0073: static final BigDecimal MAXLONG_PLUS_ONE = BigDecimal.valueOf(
0074: Long.MAX_VALUE).add(ONE);
0075: static final BigDecimal MINLONG_MINUS_ONE = BigDecimal.valueOf(
0076: Long.MIN_VALUE).subtract(ONE);
0077:
0078: /**
0079: * object state. Note that scale and precision are
0080: * always determined dynamically from value when
0081: * it is not null.
0082:
0083: The field value can be null without the data value being null.
0084: In this case the value is stored in rawData and rawScale. This
0085: is to allow the minimal amount of work to read a SQLDecimal from disk.
0086: Creating the BigDecimal is expensive as it requires allocating
0087: three objects, the last two are a waste in the case the row does
0088: not qualify or the row will be written out by the sorter before being
0089: returned to the application.
0090: <P>
0091: This means that this field must be accessed for read indirectly through
0092: the getBigDecimal() method, and when setting it the rawData field must
0093: be set to null.
0094:
0095: */
0096: private BigDecimal value;
0097:
0098: /**
0099: See comments for value
0100: */
0101: private byte[] rawData;
0102:
0103: /**
0104: See comments for value
0105: */
0106: private int rawScale;
0107:
0108: private static final int BASE_MEMORY_USAGE = ClassSize
0109: .estimateBaseFromCatalog(SQLDecimal.class);
0110: private static final int BIG_DECIMAL_MEMORY_USAGE = ClassSize
0111: .estimateBaseFromCatalog(BigDecimal.class);
0112:
0113: public int estimateMemoryUsage() {
0114: int sz = BASE_MEMORY_USAGE;
0115: if (null != value)
0116: sz += BIG_DECIMAL_MEMORY_USAGE
0117: + (value.unscaledValue().bitLength() + 8) / 8;
0118: if (null != rawData)
0119: sz += rawData.length;
0120: return sz;
0121: }
0122:
0123: ////////////////////////////////////////////////////////////////////
0124: //
0125: // CLASS INTERFACE
0126: //
0127: ////////////////////////////////////////////////////////////////////
0128: /** no-arg constructor, required by Formattable */
0129: public SQLDecimal() {
0130: }
0131:
0132: public SQLDecimal(BigDecimal val) {
0133: value = val;
0134: }
0135:
0136: public SQLDecimal(BigDecimal val, int nprecision, int scale)
0137: throws StandardException {
0138:
0139: value = val;
0140: if ((value != null) && (scale >= 0)) {
0141: value = value.setScale(scale, BigDecimal.ROUND_DOWN);
0142: }
0143: }
0144:
0145: public SQLDecimal(String val) {
0146: value = new BigDecimal(val);
0147: }
0148:
0149: /*
0150: * DataValueDescriptor interface
0151: * (mostly implemented in DataType)
0152: *
0153: */
0154:
0155: /**
0156: * @exception StandardException thrown on failure to convert
0157: */
0158: public int getInt() throws StandardException {
0159: if (isNull())
0160: return 0;
0161:
0162: try {
0163: long lv = getLong();
0164:
0165: if ((lv >= Integer.MIN_VALUE) && (lv <= Integer.MAX_VALUE))
0166: return (int) lv;
0167:
0168: } catch (StandardException se) {
0169: }
0170:
0171: throw StandardException.newException(
0172: SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "INTEGER");
0173: }
0174:
0175: /**
0176: * @exception StandardException thrown on failure to convert
0177: */
0178: public byte getByte() throws StandardException {
0179: if (isNull())
0180: return (byte) 0;
0181:
0182: try {
0183: long lv = getLong();
0184:
0185: if ((lv >= Byte.MIN_VALUE) && (lv <= Byte.MAX_VALUE))
0186: return (byte) lv;
0187:
0188: } catch (StandardException se) {
0189: }
0190:
0191: throw StandardException.newException(
0192: SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "TINYINT");
0193: }
0194:
0195: /**
0196: * @exception StandardException thrown on failure to convert
0197: */
0198: public short getShort() throws StandardException {
0199: if (isNull())
0200: return (short) 0;
0201:
0202: try {
0203: long lv = getLong();
0204:
0205: if ((lv >= Short.MIN_VALUE) && (lv <= Short.MAX_VALUE))
0206: return (short) lv;
0207:
0208: } catch (StandardException se) {
0209: }
0210:
0211: throw StandardException.newException(
0212: SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "SMALLINT");
0213: }
0214:
0215: /**
0216: * @exception StandardException thrown on failure to convert
0217: */
0218: public long getLong() throws StandardException {
0219: BigDecimal localValue = getBigDecimal();
0220: if (localValue == null)
0221: return (long) 0;
0222:
0223: // Valid range for long is
0224: // greater than Long.MIN_VALUE - 1
0225: // *and*
0226: // less than Long.MAX_VALUE + 1
0227: //
0228: // This ensures that DECIMAL values with an integral value
0229: // equal to the Long.MIN/MAX_VALUE round correctly to those values.
0230: // e.g. 9223372036854775807.1 converts to 9223372036854775807
0231: // this matches DB2 UDB behaviour
0232:
0233: if ((localValue.compareTo(SQLDecimal.MINLONG_MINUS_ONE) == 1)
0234: && (localValue.compareTo(SQLDecimal.MAXLONG_PLUS_ONE) == -1)) {
0235:
0236: return localValue.longValue();
0237: }
0238:
0239: throw StandardException.newException(
0240: SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "BIGINT");
0241: }
0242:
0243: /**
0244: * @exception StandardException thrown on failure to convert
0245: */
0246: public float getFloat() throws StandardException {
0247: BigDecimal localValue = getBigDecimal();
0248: if (localValue == null)
0249: return (float) 0;
0250:
0251: // If the BigDecimal is out of range for the float
0252: // then positive or negative infinity is returned.
0253: float value = NumberDataType.normalizeREAL(localValue
0254: .floatValue());
0255:
0256: return value;
0257: }
0258:
0259: /**
0260: *
0261: * If we have a value that is greater than the maximum double,
0262: * exception is thrown. Otherwise, ok. If the value is less
0263: * than can be represented by a double, ti will get set to
0264: * the smallest double value.
0265: *
0266: * @exception StandardException thrown on failure to convert
0267: */
0268: public double getDouble() throws StandardException {
0269: BigDecimal localValue = getBigDecimal();
0270: if (localValue == null)
0271: return (double) 0;
0272:
0273: // If the BigDecimal is out of range for double
0274: // then positive or negative infinity is returned.
0275: double value = NumberDataType.normalizeDOUBLE(localValue
0276: .doubleValue());
0277: return value;
0278: }
0279:
0280: private BigDecimal getBigDecimal() {
0281: if ((value == null) && (rawData != null)) {
0282: value = new BigDecimal(new BigInteger(rawData), rawScale);
0283: }
0284:
0285: return value;
0286: }
0287:
0288: /**
0289: * DECIMAL implementation. Convert to a BigDecimal using getObject
0290: * which will return a BigDecimal
0291: */
0292: public int typeToBigDecimal() {
0293: return java.sql.Types.DECIMAL;
0294: }
0295:
0296: // 0 or null is false, all else is true
0297: public boolean getBoolean() {
0298:
0299: BigDecimal localValue = getBigDecimal();
0300: if (localValue == null)
0301: return false;
0302:
0303: return localValue.compareTo(ZERO) != 0;
0304: }
0305:
0306: public String getString() {
0307: BigDecimal localValue = getBigDecimal();
0308: if (localValue == null)
0309: return null;
0310: else if (JVMInfo.JDK_ID < JVMInfo.J2SE_15)
0311: return localValue.toString();
0312: else {
0313: // use reflection so we can still compile using JDK1.4
0314: // if we are prepared to require 1.5 to compile then this can be a direct call
0315: try {
0316: return (String) toPlainString.invoke(localValue, null);
0317: } catch (IllegalAccessException e) {
0318: // can't happen based on the JDK spec
0319: throw new IllegalAccessError("toPlainString");
0320: } catch (InvocationTargetException e) {
0321: Throwable t = e.getTargetException();
0322: if (t instanceof RuntimeException) {
0323: throw (RuntimeException) t;
0324: } else if (t instanceof Error) {
0325: throw (Error) t;
0326: } else {
0327: // can't happen
0328: throw new IncompatibleClassChangeError(
0329: "toPlainString");
0330: }
0331: }
0332: }
0333: }
0334:
0335: private static final Method toPlainString;
0336: private static final Method bdPrecision;
0337: static {
0338: Method m;
0339: try {
0340: m = BigDecimal.class.getMethod("toPlainString", null);
0341: } catch (NoSuchMethodException e) {
0342: m = null;
0343: }
0344: toPlainString = m;
0345: try {
0346: m = BigDecimal.class.getMethod("precision", null);
0347: } catch (NoSuchMethodException e) {
0348: m = null;
0349: }
0350: bdPrecision = m;
0351: }
0352:
0353: public Object getObject() {
0354: /*
0355: ** BigDecimal is immutable
0356: */
0357: return getBigDecimal();
0358: }
0359:
0360: /**
0361: * Set the value from a correctly typed BigDecimal object.
0362: * @throws StandardException
0363: */
0364: void setObject(Object theValue) throws StandardException {
0365: setValue((BigDecimal) theValue);
0366: }
0367:
0368: protected void setFrom(DataValueDescriptor theValue)
0369: throws StandardException {
0370:
0371: setCoreValue(SQLDecimal.getBigDecimal(theValue));
0372: }
0373:
0374: public int getLength() {
0375: return getDecimalValuePrecision();
0376: }
0377:
0378: // this is for DataType's error generator
0379: public String getTypeName() {
0380: return TypeId.DECIMAL_NAME;
0381: }
0382:
0383: /*
0384: * Storable interface, implies Externalizable, TypedFormat
0385: */
0386:
0387: /**
0388: Return my format identifier.
0389:
0390: @see org.apache.derby.iapi.services.io.TypedFormat#getTypeFormatId
0391: */
0392: public int getTypeFormatId() {
0393: return StoredFormatIds.SQL_DECIMAL_ID;
0394: }
0395:
0396: /*
0397: * see if the decimal value is null.
0398: */
0399: /** @see Storable#isNull */
0400: public boolean isNull() {
0401: return (value == null) && (rawData == null);
0402: }
0403:
0404: /**
0405: * Distill the BigDecimal to a byte array and
0406: * write out: <UL>
0407: * <LI> scale (zero or positive) as a byte </LI>
0408: * <LI> length of byte array as a byte</LI>
0409: * <LI> the byte array </LI> </UL>
0410: *
0411: */
0412: public void writeExternal(ObjectOutput out) throws IOException {
0413: // never called when value is null
0414: if (SanityManager.DEBUG)
0415: SanityManager.ASSERT(!isNull());
0416:
0417: int scale;
0418: byte[] byteArray;
0419:
0420: if (value != null) {
0421: scale = value.scale();
0422:
0423: // J2SE 5.0 introduced negative scale value for BigDecimals.
0424: // In previouse Java releases a negative scale was not allowed
0425: // (threw an exception on setScale and the constructor that took
0426: // a scale).
0427: //
0428: // Thus the Derby format for DECIMAL implictly assumed a
0429: // positive or zero scale value, and thus now must explicitly
0430: // be positive. This is to allow databases created under J2SE 5.0
0431: // to continue to be supported under JDK 1.3/JDK 1.4, ie. to continue
0432: // the platform independence, independent of OS/cpu and JVM.
0433: //
0434: // If the scale is negative set the scale to be zero, this results
0435: // in an unchanged value with a new scale. A BigDecimal with a
0436: // negative scale by definition is a whole number.
0437: // e.g. 1000 can be represented by:
0438: // a BigDecimal with scale -3 (unscaled value of 1)
0439: // or a BigDecimal with scale 0 (unscaled value of 1000)
0440:
0441: if (scale < 0) {
0442: scale = 0;
0443: value = value.setScale(0);
0444: }
0445:
0446: BigInteger bi = value.unscaledValue();
0447: byteArray = bi.toByteArray();
0448: } else {
0449: scale = rawScale;
0450: byteArray = rawData;
0451: }
0452:
0453: if (SanityManager.DEBUG) {
0454: if (scale < 0)
0455: SanityManager
0456: .THROWASSERT("DECIMAL scale at writeExternal is negative "
0457: + scale + " value " + toString());
0458: }
0459:
0460: out.writeByte(scale);
0461: out.writeByte(byteArray.length);
0462: out.write(byteArray);
0463: }
0464:
0465: /**
0466: * Note the use of rawData: we reuse the array if the
0467: * incoming array is the same length or smaller than
0468: * the array length.
0469: *
0470: * @see java.io.Externalizable#readExternal
0471: */
0472: public void readExternal(ObjectInput in) throws IOException {
0473: // clear the previous value to ensure that the
0474: // rawData value will be used
0475: value = null;
0476:
0477: rawScale = in.readUnsignedByte();
0478: int size = in.readUnsignedByte();
0479:
0480: /*
0481: ** Allocate a new array if the data to read
0482: ** is larger than the existing array, or if
0483: ** we don't have an array yet.
0484:
0485: Need to use readFully below and NOT just read because read does not
0486: guarantee getting size bytes back, whereas readFully does (unless EOF).
0487: */
0488: if ((rawData == null) || size != rawData.length) {
0489: rawData = new byte[size];
0490: }
0491: in.readFully(rawData);
0492:
0493: }
0494:
0495: public void readExternalFromArray(ArrayInputStream in)
0496: throws IOException {
0497: // clear the previous value to ensure that the
0498: // rawData value will be used
0499: value = null;
0500:
0501: rawScale = in.readUnsignedByte();
0502: int size = in.readUnsignedByte();
0503:
0504: /*
0505: ** Allocate a new array if the data to read
0506: ** is larger than the existing array, or if
0507: ** we don't have an array yet.
0508:
0509: Need to use readFully below and NOT just read because read does not
0510: guarantee getting size bytes back, whereas readFully does (unless EOF).
0511: */
0512: if ((rawData == null) || size != rawData.length) {
0513: rawData = new byte[size];
0514: }
0515: in.readFully(rawData);
0516: }
0517:
0518: /**
0519: * @see Storable#restoreToNull
0520: *
0521: */
0522: public void restoreToNull() {
0523: value = null;
0524: rawData = null;
0525: }
0526:
0527: /** @exception StandardException Thrown on error */
0528: protected int typeCompare(DataValueDescriptor arg)
0529: throws StandardException {
0530: BigDecimal otherValue = SQLDecimal.getBigDecimal(arg);
0531: return getBigDecimal().compareTo(otherValue);
0532: }
0533:
0534: /*
0535: * DataValueDescriptor interface
0536: */
0537:
0538: /**
0539: * <B> WARNING </B> clone is a shallow copy
0540: * @see DataValueDescriptor#getClone
0541: */
0542: public DataValueDescriptor getClone() {
0543: return new SQLDecimal(getBigDecimal());
0544: }
0545:
0546: /**
0547: * @see DataValueDescriptor#getNewNull
0548: */
0549: public DataValueDescriptor getNewNull() {
0550: return new SQLDecimal();
0551: }
0552:
0553: /**
0554: * @see DataValueDescriptor#setValueFromResultSet
0555: *
0556: * @exception SQLException Thrown on error
0557: */
0558: public void setValueFromResultSet(ResultSet resultSet,
0559: int colNumber, boolean isNullable) throws SQLException {
0560: value = resultSet.getBigDecimal(colNumber);
0561: rawData = null;
0562: }
0563:
0564: /**
0565: Set the value into a PreparedStatement.
0566:
0567: @exception SQLException Error setting value in PreparedStatement
0568: */
0569: public final void setInto(PreparedStatement ps, int position)
0570: throws SQLException {
0571:
0572: if (isNull()) {
0573: ps.setNull(position, java.sql.Types.DECIMAL);
0574: return;
0575: }
0576:
0577: ps.setBigDecimal(position, getBigDecimal());
0578: }
0579:
0580: /**
0581: *
0582: * <B> WARNING </B> there is no checking to make sure
0583: * that theValue doesn't exceed the precision/scale of
0584: * the current SQLDecimal. It is just assumed that the
0585: * SQLDecimal is supposed to take the precision/scale of
0586: * the BigDecimalized String.
0587: *
0588: * @exception StandardException throws NumberFormatException
0589: * when the String format is not recognized.
0590: */
0591: public void setValue(String theValue) throws StandardException {
0592: rawData = null;
0593:
0594: if (theValue == null) {
0595: value = null;
0596: } else {
0597: try {
0598: theValue = theValue.trim();
0599: value = new BigDecimal(theValue);
0600: rawData = null;
0601: } catch (NumberFormatException nfe) {
0602: throw invalidFormat();
0603: }
0604: }
0605: }
0606:
0607: /**
0608: * @see NumberDataValue#setValue
0609: *
0610: * @exception StandardException Thrown on error
0611: */
0612: public void setValue(double theValue) throws StandardException {
0613: setCoreValue(NumberDataType.normalizeDOUBLE(theValue));
0614: }
0615:
0616: /**
0617: * @see NumberDataValue#setValue
0618: *
0619: */
0620: public void setValue(float theValue) throws StandardException {
0621: setCoreValue((double) NumberDataType.normalizeREAL(theValue));
0622: }
0623:
0624: /**
0625: * @see NumberDataValue#setValue
0626: *
0627: */
0628: public void setValue(long theValue) {
0629: value = BigDecimal.valueOf(theValue);
0630: rawData = null;
0631: }
0632:
0633: /**
0634: * @see NumberDataValue#setValue
0635: *
0636: */
0637: public void setValue(int theValue) {
0638: setValue((long) theValue);
0639: }
0640:
0641: /**
0642: Only to be called when the application sets a value using BigDecimal
0643: through setBigDecimal calls.
0644: */
0645: public void setBigDecimal(Number theValue) throws StandardException {
0646: setCoreValue((BigDecimal) theValue);
0647: }
0648:
0649: /**
0650: Called when setting a DECIMAL value internally or from
0651: through a procedure or function.
0652: Handles long in addition to BigDecimal to handle
0653: identity being stored as a long but returned as a DECIMAL.
0654: */
0655: public void setValue(Number theValue) throws StandardException {
0656: if (SanityManager.ASSERT) {
0657: if (theValue != null
0658: && !(theValue instanceof java.math.BigDecimal)
0659: && !(theValue instanceof java.lang.Long))
0660: SanityManager
0661: .THROWASSERT("SQLDecimal.setValue(Number) passed a "
0662: + theValue.getClass());
0663: }
0664:
0665: if (theValue instanceof BigDecimal || theValue == null)
0666: setCoreValue((BigDecimal) theValue);
0667: else
0668: setValue(theValue.longValue());
0669: }
0670:
0671: /**
0672: * @see NumberDataValue#setValue
0673: *
0674: */
0675: public void setValue(boolean theValue) {
0676: setCoreValue(theValue ? ONE : ZERO);
0677: }
0678:
0679: /*
0680: * DataValueDescriptor interface
0681: */
0682:
0683: /** @see DataValueDescriptor#typePrecedence */
0684: public int typePrecedence() {
0685: return TypeId.DECIMAL_PRECEDENCE;
0686: }
0687:
0688: // END DataValueDescriptor interface
0689:
0690: private void setCoreValue(BigDecimal theValue) {
0691: value = theValue;
0692: rawData = null;
0693: }
0694:
0695: private void setCoreValue(double theValue) {
0696: value = new BigDecimal(Double.toString(theValue));
0697: rawData = null;
0698: }
0699:
0700: /**
0701: * Normalization method - this method may be called when putting
0702: * a value into a SQLDecimal, for example, when inserting into a SQLDecimal
0703: * column. See NormalizeResultSet in execution.
0704: * <p>
0705: * Note that truncation is allowed on the decimal portion
0706: * of a numeric only.
0707: *
0708: * @param desiredType The type to normalize the source column to
0709: * @param source The value to normalize
0710: *
0711: * @throws StandardException Thrown for null into
0712: * non-nullable column, and for
0713: * truncation error
0714: */
0715: public void normalize(DataTypeDescriptor desiredType,
0716: DataValueDescriptor source) throws StandardException {
0717: int desiredScale = desiredType.getScale();
0718: int desiredPrecision = desiredType.getPrecision();
0719:
0720: setFrom(source);
0721: setWidth(desiredPrecision, desiredScale, true);
0722: }
0723:
0724: /*
0725: ** SQL Operators
0726: */
0727:
0728: /**
0729: * This method implements the + operator for DECIMAL.
0730: *
0731: * @param addend1 One of the addends
0732: * @param addend2 The other addend
0733: * @param result The result of a previous call to this method, null
0734: * if not called yet
0735: *
0736: * @return A SQLDecimal containing the result of the addition
0737: *
0738: * @exception StandardException Thrown on error
0739: */
0740:
0741: public NumberDataValue plus(NumberDataValue addend1,
0742: NumberDataValue addend2, NumberDataValue result)
0743: throws StandardException {
0744: if (result == null) {
0745: result = new SQLDecimal();
0746: }
0747:
0748: if (addend1.isNull() || addend2.isNull()) {
0749: result.setToNull();
0750: return result;
0751: }
0752:
0753: result.setBigDecimal(SQLDecimal.getBigDecimal(addend1).add(
0754: SQLDecimal.getBigDecimal(addend2)));
0755: return result;
0756: }
0757:
0758: /**
0759: * This method implements the - operator for "decimal - decimal".
0760: *
0761: * @param left The value to be subtracted from
0762: * @param right The value to be subtracted
0763: * @param result The result of a previous call to this method, null
0764: * if not called yet
0765: *
0766: * @return A SQLDecimal containing the result of the subtraction
0767: *
0768: * @exception StandardException Thrown on error
0769: */
0770:
0771: public NumberDataValue minus(NumberDataValue left,
0772: NumberDataValue right, NumberDataValue result)
0773: throws StandardException {
0774: if (result == null) {
0775: result = new SQLDecimal();
0776: }
0777:
0778: if (left.isNull() || right.isNull()) {
0779: result.setToNull();
0780: return result;
0781: }
0782:
0783: result.setBigDecimal(SQLDecimal.getBigDecimal(left).subtract(
0784: SQLDecimal.getBigDecimal(right)));
0785: return result;
0786: }
0787:
0788: /**
0789: * This method implements the * operator for "double * double".
0790: *
0791: * @param left The first value to be multiplied
0792: * @param right The second value to be multiplied
0793: * @param result The result of a previous call to this method, null
0794: * if not called yet
0795: *
0796: * @return A SQLDecimal containing the result of the multiplication
0797: *
0798: * @exception StandardException Thrown on error
0799: */
0800:
0801: public NumberDataValue times(NumberDataValue left,
0802: NumberDataValue right, NumberDataValue result)
0803: throws StandardException {
0804: if (result == null) {
0805: result = new SQLDecimal();
0806: }
0807:
0808: if (left.isNull() || right.isNull()) {
0809: result.setToNull();
0810: return result;
0811: }
0812:
0813: result.setBigDecimal(SQLDecimal.getBigDecimal(left).multiply(
0814: SQLDecimal.getBigDecimal(right)));
0815: return result;
0816: }
0817:
0818: /**
0819: * This method implements the / operator for BigDecimal/BigDecimal
0820: *
0821: * @param dividend The numerator
0822: * @param divisor The denominator
0823: * @param result The result of a previous call to this method, null
0824: * if not called yet
0825: *
0826: * @return A SQLDecimal containing the result of the division
0827: *
0828: * @exception StandardException Thrown on error
0829: */
0830:
0831: public NumberDataValue divide(NumberDataValue dividend,
0832: NumberDataValue divisor, NumberDataValue result)
0833: throws StandardException {
0834: return divide(dividend, divisor, result, -1);
0835: }
0836:
0837: /**
0838: * This method implements the / operator for BigDecimal/BigDecimal
0839: *
0840: * @param dividend The numerator
0841: * @param divisor The denominator
0842: * @param result The result of a previous call to this method, null
0843: * if not called yet
0844: * @param scale The result scale, if < 0, calculate the scale according
0845: * to the actual values' sizes
0846: *
0847: * @return A SQLDecimal containing the result of the division
0848: *
0849: * @exception StandardException Thrown on error
0850: */
0851:
0852: public NumberDataValue divide(NumberDataValue dividend,
0853: NumberDataValue divisor, NumberDataValue result, int scale)
0854: throws StandardException {
0855: if (result == null) {
0856: result = new SQLDecimal();
0857: }
0858:
0859: if (dividend.isNull() || divisor.isNull()) {
0860: result.setToNull();
0861: return result;
0862: }
0863:
0864: BigDecimal divisorBigDecimal = SQLDecimal
0865: .getBigDecimal(divisor);
0866:
0867: if (divisorBigDecimal.compareTo(ZERO) == 0) {
0868: throw StandardException
0869: .newException(SQLState.LANG_DIVIDE_BY_ZERO);
0870: }
0871: BigDecimal dividendBigDecimal = SQLDecimal
0872: .getBigDecimal(dividend);
0873:
0874: /*
0875: ** Set the result scale to be either the passed in scale, whcih was
0876: ** calculated at bind time to be max(ls+rp-rs+1, 4), where ls,rp,rs
0877: ** are static data types' sizes, which are predictable and stable
0878: ** (for the whole result set column, eg.); otherwise dynamically
0879: ** calculates the scale according to actual values. Beetle 3901
0880: */
0881: result
0882: .setBigDecimal(dividendBigDecimal
0883: .divide(
0884: divisorBigDecimal,
0885: scale > -1 ? scale
0886: : Math
0887: .max(
0888: (dividendBigDecimal
0889: .scale()
0890: + SQLDecimal
0891: .getWholeDigits(divisorBigDecimal) + 1),
0892: NumberDataValue.MIN_DECIMAL_DIVIDE_SCALE),
0893: BigDecimal.ROUND_DOWN));
0894:
0895: return result;
0896: }
0897:
0898: /**
0899: * This method implements the unary minus operator for double.
0900: *
0901: * @param result The result of a previous call to this method, null
0902: * if not called yet
0903: *
0904: * @return A SQLDecimal containing the result of the division
0905: *
0906: * @exception StandardException Thrown on error
0907: */
0908:
0909: public NumberDataValue minus(NumberDataValue result)
0910: throws StandardException {
0911: if (result == null) {
0912: result = new SQLDecimal();
0913: }
0914:
0915: if (this .isNull()) {
0916: result.setToNull();
0917: return result;
0918: }
0919:
0920: result.setBigDecimal(getBigDecimal().negate());
0921: return result;
0922: }
0923:
0924: /**
0925: * This method implements the isNegative method.
0926: *
0927: * @return A boolean. If this.value is negative, return true.
0928: * For positive values or null, return false.
0929: */
0930:
0931: protected boolean isNegative() {
0932: return !isNull() && (getBigDecimal().compareTo(ZERO) == -1);
0933: }
0934:
0935: /*
0936: * String display of value
0937: */
0938: public String toString() {
0939: if (isNull())
0940: return "NULL";
0941: else
0942: return getString();
0943: }
0944:
0945: /*
0946: * Hash code
0947: */
0948: public int hashCode() {
0949: long longVal;
0950: BigDecimal localValue = getBigDecimal();
0951:
0952: double doubleVal = (localValue != null) ? localValue
0953: .doubleValue() : 0;
0954:
0955: if (Double.isInfinite(doubleVal)) {
0956: /*
0957: ** This loses the fractional part, but it probably doesn't
0958: ** matter for numbers that are big enough to overflow a double -
0959: ** it's probably rare for numbers this big to be different only in
0960: ** their fractional parts.
0961: */
0962: longVal = localValue.longValue();
0963: } else {
0964: longVal = (long) doubleVal;
0965: if (longVal != doubleVal) {
0966: longVal = Double.doubleToLongBits(doubleVal);
0967: }
0968: }
0969:
0970: return (int) (longVal ^ (longVal >> 32));
0971: }
0972:
0973: ///////////////////////////////////////////////////////////////////////////////
0974: //
0975: // VariableSizeDataValue interface
0976: //
0977: ///////////////////////////////////////////////////////////////////////////////
0978:
0979: /**
0980: * Set the precision/scale of the to the desired values.
0981: * Used when CASTing. Ideally we'd recycle normalize(), but
0982: * the use is different.
0983: *
0984: * @param desiredPrecision the desired precision -- IGNORE_PREICISION
0985: * if it is to be ignored.
0986: * @param desiredScale the desired scale
0987: * @param errorOnTrunc throw error on truncation (ignored --
0988: * always thrown if we truncate the non-decimal part of
0989: * the value)
0990: *
0991: * @exception StandardException Thrown on non-zero truncation
0992: * if errorOnTrunc is true
0993: */
0994: public void setWidth(int desiredPrecision, int desiredScale,
0995: boolean errorOnTrunc) throws StandardException {
0996: if (isNull())
0997: return;
0998:
0999: if (desiredPrecision != IGNORE_PRECISION
1000: && ((desiredPrecision - desiredScale) < SQLDecimal
1001: .getWholeDigits(getBigDecimal()))) {
1002: throw StandardException.newException(
1003: SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE,
1004: ("DECIMAL/NUMERIC(" + desiredPrecision + ","
1005: + desiredScale + ")"));
1006: }
1007: value = value.setScale(desiredScale, BigDecimal.ROUND_DOWN);
1008: rawData = null;
1009: }
1010:
1011: /**
1012: * Return the SQL scale of this value, number of digits after the
1013: * decimal point, or zero for a whole number. This does not match the
1014: * return from BigDecimal.scale() since in J2SE 5.0 onwards that can return
1015: * negative scales.
1016: */
1017: public int getDecimalValuePrecision() {
1018: if (isNull())
1019: return 0;
1020:
1021: BigDecimal localValue = getBigDecimal();
1022:
1023: return SQLDecimal.getWholeDigits(localValue)
1024: + getDecimalValueScale();
1025: }
1026:
1027: /**
1028: * Return the SQL scale of this value, number of digits after the
1029: * decimal point, or zero for a whole number. This does not match the
1030: * return from BigDecimal.scale() since in J2SE 5.0 onwards that can return
1031: * negative scales.
1032: */
1033: public int getDecimalValueScale() {
1034: if (isNull())
1035: return 0;
1036:
1037: if (value == null)
1038: return rawScale;
1039:
1040: int scale = value.scale();
1041: if (scale >= 0)
1042: return scale;
1043:
1044: // BigDecimal scale is negative, so number must have no fractional
1045: // part as its value is the unscaled value * 10^-scale
1046: return 0;
1047: }
1048:
1049: /**
1050: * Get a BigDecimal representing the value of a DataValueDescriptor
1051: * @param value Non-null value to be converted
1052: * @return BigDecimal value
1053: * @throws StandardException Invalid conversion or out of range.
1054: */
1055: public static BigDecimal getBigDecimal(DataValueDescriptor value)
1056: throws StandardException {
1057: if (SanityManager.DEBUG) {
1058: if (value.isNull())
1059: SanityManager
1060: .THROWASSERT("NULL value passed to SQLDecimal.getBigDecimal");
1061: }
1062:
1063: switch (value.typeToBigDecimal()) {
1064: case Types.DECIMAL:
1065: return (BigDecimal) value.getObject();
1066: case Types.CHAR:
1067: try {
1068: return new BigDecimal(value.getString().trim());
1069: } catch (NumberFormatException nfe) {
1070: throw StandardException.newException(
1071: SQLState.LANG_FORMAT_EXCEPTION,
1072: "java.math.BigDecimal");
1073: }
1074: case Types.BIGINT:
1075: return BigDecimal.valueOf(value.getLong());
1076: default:
1077: if (SanityManager.DEBUG)
1078: SanityManager.THROWASSERT("invalid return from "
1079: + value.getClass() + ".typeToBigDecimal() "
1080: + value.typeToBigDecimal());
1081: return null;
1082: }
1083: }
1084:
1085: /**
1086: * Calculate the number of digits to the left of the decimal point
1087: * of the passed in value.
1088: * @param decimalValue Value to get whole digits from, never null.
1089: * @return number of whole digits.
1090: */
1091: private static int getWholeDigits(BigDecimal decimalValue) {
1092: /**
1093: * if ONE > abs(value) then the number of whole digits is 0
1094: */
1095: decimalValue = decimalValue.abs();
1096: if (ONE.compareTo(decimalValue) == 1) {
1097: return 0;
1098: }
1099:
1100: if (JVMInfo.JDK_ID >= JVMInfo.J2SE_15) {
1101: // use reflection so we can still compile using JDK1.4
1102: // if we are prepared to require 1.5 to compile then this can be a
1103: // direct call
1104: try {
1105: // precision is the number of digits in the unscaled value,
1106: // subtracting the scale (positive or negative) will give the
1107: // number of whole digits.
1108: int precision = ((Integer) bdPrecision.invoke(
1109: decimalValue, null)).intValue();
1110: return precision - decimalValue.scale();
1111: } catch (IllegalAccessException e) {
1112: // can't happen based on the JDK spec
1113: throw new IllegalAccessError("precision");
1114: } catch (InvocationTargetException e) {
1115: Throwable t = e.getTargetException();
1116: if (t instanceof RuntimeException) {
1117: throw (RuntimeException) t;
1118: } else if (t instanceof Error) {
1119: throw (Error) t;
1120: } else {
1121: // can't happen
1122: throw new IncompatibleClassChangeError("precision");
1123: }
1124: }
1125:
1126: }
1127:
1128: String s = decimalValue.toString();
1129: return (decimalValue.scale() == 0) ? s.length() : s
1130: .indexOf('.');
1131: }
1132: }
|