| /* |
| |
| Derby - Class org.apache.derby.iapi.types.SQLDecimal |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to you under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| package org.apache.derby.iapi.types; |
| |
| import org.apache.derby.iapi.reference.SQLState; |
| |
| import org.apache.derby.shared.common.sanity.SanityManager; |
| |
| import org.apache.derby.iapi.services.io.StoredFormatIds; |
| import org.apache.derby.iapi.services.io.Storable; |
| |
| import org.apache.derby.iapi.error.StandardException; |
| |
| import org.apache.derby.iapi.services.cache.ClassSize; |
| |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.math.RoundingMode; |
| import java.io.ObjectOutput; |
| import java.io.ObjectInput; |
| import java.io.IOException; |
| |
| import java.sql.ResultSet; |
| import java.sql.PreparedStatement; |
| import java.sql.SQLException; |
| import java.sql.Types; |
| |
| /** |
| * SQLDecimal satisfies the DataValueDescriptor |
| * interfaces (i.e., OrderableDataType). It implements a numeric/decimal column, |
| * e.g. for * storing a column value; it can be specified |
| * when constructed to not allow nulls. Nullability cannot be changed |
| * after construction, as it affects the storage size and mechanism. |
| * <p> |
| * Because OrderableDataType is a subtype of DataType, |
| * SQLDecimal can play a role in either a DataType/Row |
| * or a OrderableDataType/Row, interchangeably. |
| * <p> |
| * We assume the store has a flag for nullness of the value, |
| * and simply return a 0-length array for the stored form |
| * when the value is null. |
| * |
| */ |
| public final class SQLDecimal extends NumberDataType implements VariableSizeDataValue |
| { |
| /** |
| * object state. Note that scale and precision are |
| * always determined dynamically from value when |
| * it is not null. |
| |
| The field value can be null without the data value being null. |
| In this case the value is stored in rawData and rawScale. This |
| is to allow the minimal amount of work to read a SQLDecimal from disk. |
| Creating the BigDecimal is expensive as it requires allocating |
| three objects, the last two are a waste in the case the row does |
| not qualify or the row will be written out by the sorter before being |
| returned to the application. |
| <P> |
| This means that this field must be accessed for read indirectly through |
| the getBigDecimal() method, and when setting it the rawData field must |
| be set to null. |
| |
| */ |
| private BigDecimal value; |
| |
| /** |
| See comments for value |
| */ |
| private byte[] rawData; |
| |
| /** |
| See comments for value |
| */ |
| private int rawScale; |
| |
| private static final int BASE_MEMORY_USAGE = ClassSize.estimateBaseFromCatalog( SQLDecimal.class); |
| private static final int BIG_DECIMAL_MEMORY_USAGE = ClassSize.estimateBaseFromCatalog( BigDecimal.class); |
| |
| public int estimateMemoryUsage() |
| { |
| int sz = BASE_MEMORY_USAGE; |
| if( null != value) |
| sz += BIG_DECIMAL_MEMORY_USAGE + (value.unscaledValue().bitLength() + 8)/8; |
| if( null != rawData) |
| sz += rawData.length; |
| return sz; |
| } |
| |
| |
| //////////////////////////////////////////////////////////////////// |
| // |
| // CLASS INTERFACE |
| // |
| //////////////////////////////////////////////////////////////////// |
| /** no-arg constructor, required by Formattable */ |
| public SQLDecimal() |
| { |
| } |
| |
| public SQLDecimal(BigDecimal val) |
| { |
| value = val; |
| } |
| |
| public SQLDecimal(BigDecimal val, int nprecision, int scale) |
| throws StandardException |
| { |
| |
| value = val; |
| if ((value != null) && (scale >= 0)) |
| { |
| value = value.setScale(scale, RoundingMode.DOWN); |
| } |
| } |
| |
| public SQLDecimal(String val) |
| { |
| value = new BigDecimal(val); |
| } |
| |
| /* |
| * DataValueDescriptor interface |
| * (mostly implemented in DataType) |
| * |
| */ |
| |
| |
| /** |
| * @exception StandardException thrown on failure to convert |
| */ |
| public int getInt() throws StandardException |
| { |
| if (isNull()) |
| return 0; |
| |
| try { |
| long lv = getLong(); |
| |
| if ((lv >= Integer.MIN_VALUE) && (lv <= Integer.MAX_VALUE)) |
| return (int) lv; |
| |
| } catch (StandardException se) { |
| } |
| |
| throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "INTEGER"); |
| } |
| |
| /** |
| * @exception StandardException thrown on failure to convert |
| */ |
| public byte getByte() throws StandardException |
| { |
| if (isNull()) |
| return (byte)0; |
| |
| try { |
| long lv = getLong(); |
| |
| if ((lv >= Byte.MIN_VALUE) && (lv <= Byte.MAX_VALUE)) |
| return (byte) lv; |
| |
| } catch (StandardException se) { |
| } |
| |
| throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "TINYINT"); |
| } |
| |
| /** |
| * @exception StandardException thrown on failure to convert |
| */ |
| public short getShort() throws StandardException |
| { |
| if (isNull()) |
| return (short)0; |
| |
| try { |
| long lv = getLong(); |
| |
| if ((lv >= Short.MIN_VALUE) && (lv <= Short.MAX_VALUE)) |
| return (short) lv; |
| |
| } catch (StandardException se) { |
| } |
| |
| throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "SMALLINT"); |
| } |
| |
| /** |
| * @exception StandardException thrown on failure to convert |
| */ |
| public long getLong() throws StandardException |
| { |
| BigDecimal localValue = getBigDecimal(); |
| if (localValue == null) |
| return (long)0; |
| |
| // Valid range for long is |
| // greater than Long.MIN_VALUE - 1 |
| // *and* |
| // less than Long.MAX_VALUE + 1 |
| // |
| // This ensures that DECIMAL values with an integral value |
| // equal to the Long.MIN/MAX_VALUE round correctly to those values. |
| // e.g. 9223372036854775807.1 converts to 9223372036854775807 |
| // this matches DB2 UDB behaviour |
| |
| if ( (localValue.compareTo(MINLONG_MINUS_ONE) == 1) |
| && (localValue.compareTo(MAXLONG_PLUS_ONE) == -1)) { |
| |
| return localValue.longValue(); |
| } |
| |
| throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "BIGINT"); |
| } |
| |
| /** |
| * @exception StandardException thrown on failure to convert |
| */ |
| public float getFloat() throws StandardException |
| { |
| BigDecimal localValue = getBigDecimal(); |
| if (localValue == null) |
| return (float)0; |
| |
| // If the BigDecimal is out of range for the float |
| // then positive or negative infinity is returned. |
| float value = NumberDataType.normalizeREAL(localValue.floatValue()); |
| |
| return value; |
| } |
| |
| /** |
| * |
| * If we have a value that is greater than the maximum double, |
| * exception is thrown. Otherwise, ok. If the value is less |
| * than can be represented by a double, ti will get set to |
| * the smallest double value. |
| * |
| * @exception StandardException thrown on failure to convert |
| */ |
| public double getDouble() throws StandardException |
| { |
| BigDecimal localValue = getBigDecimal(); |
| if (localValue == null) |
| return (double)0; |
| |
| // If the BigDecimal is out of range for double |
| // then positive or negative infinity is returned. |
| double value = NumberDataType.normalizeDOUBLE(localValue.doubleValue()); |
| return value; |
| } |
| |
| private BigDecimal getBigDecimal() |
| { |
| if ((value == null) && (rawData != null)) |
| { |
| value = new BigDecimal(new BigInteger(rawData), rawScale); |
| } |
| |
| return value; |
| } |
| |
| /** |
| * DECIMAL implementation. Convert to a BigDecimal using getObject |
| * which will return a BigDecimal |
| */ |
| public int typeToBigDecimal() |
| { |
| return java.sql.Types.DECIMAL; |
| } |
| |
| // 0 or null is false, all else is true |
| public boolean getBoolean() |
| { |
| |
| BigDecimal localValue = getBigDecimal(); |
| if (localValue == null) |
| return false; |
| |
| return localValue.compareTo(BigDecimal.ZERO) != 0; |
| } |
| |
| public String getString() |
| { |
| BigDecimal localValue = getBigDecimal(); |
| return (localValue == null) ? null : localValue.toPlainString(); |
| } |
| |
| public Object getObject() |
| { |
| /* |
| ** BigDecimal is immutable |
| */ |
| return getBigDecimal(); |
| } |
| |
| /** |
| * Set the value from a correctly typed BigDecimal object. |
| * @throws StandardException |
| */ |
| void setObject(Object theValue) throws StandardException |
| { |
| setValue((BigDecimal) theValue); |
| } |
| |
| protected void setFrom(DataValueDescriptor theValue) throws StandardException { |
| |
| setCoreValue(SQLDecimal.getBigDecimal(theValue)); |
| } |
| |
| public int getLength() |
| { |
| return getDecimalValuePrecision(); |
| } |
| |
| // this is for DataType's error generator |
| public String getTypeName() |
| { |
| return TypeId.DECIMAL_NAME; |
| } |
| |
| /* |
| * Storable interface, implies Externalizable, TypedFormat |
| */ |
| |
| /** |
| Return my format identifier. |
| |
| @see org.apache.derby.iapi.services.io.TypedFormat#getTypeFormatId |
| */ |
| public int getTypeFormatId() |
| { |
| return StoredFormatIds.SQL_DECIMAL_ID; |
| } |
| |
| /* |
| * see if the decimal value is null. |
| */ |
| /** @see Storable#isNull */ |
| public boolean isNull() |
| { |
| return (value == null) && (rawData == null); |
| } |
| |
| /** |
| * Distill the BigDecimal to a byte array and |
| * write out: <UL> |
| * <LI> scale (zero or positive) as a byte </LI> |
| * <LI> length of byte array as a byte</LI> |
| * <LI> the byte array </LI> </UL> |
| * |
| */ |
| public void writeExternal(ObjectOutput out) throws IOException |
| { |
| // never called when value is null |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(! isNull()); |
| |
| int scale; |
| byte[] byteArray; |
| |
| if (value != null) { |
| scale = value.scale(); |
| |
| // J2SE 5.0 introduced negative scale value for BigDecimals. |
| // In previouse Java releases a negative scale was not allowed |
| // (threw an exception on setScale and the constructor that took |
| // a scale). |
| // |
| // Thus the Derby format for DECIMAL implictly assumed a |
| // positive or zero scale value, and thus now must explicitly |
| // be positive. This is to allow databases created under J2SE 5.0 |
| // to continue to be supported under JDK 1.3/JDK 1.4, ie. to continue |
| // the platform independence, independent of OS/cpu and JVM. |
| // |
| // If the scale is negative set the scale to be zero, this results |
| // in an unchanged value with a new scale. A BigDecimal with a |
| // negative scale by definition is a whole number. |
| // e.g. 1000 can be represented by: |
| // a BigDecimal with scale -3 (unscaled value of 1) |
| // or a BigDecimal with scale 0 (unscaled value of 1000) |
| |
| if (scale < 0) { |
| scale = 0; |
| value = value.setScale(0); |
| } |
| |
| BigInteger bi = value.unscaledValue(); |
| byteArray = bi.toByteArray(); |
| } else { |
| scale = rawScale; |
| byteArray = rawData; |
| } |
| |
| if (SanityManager.DEBUG) |
| { |
| if (scale < 0) |
| SanityManager.THROWASSERT("DECIMAL scale at writeExternal is negative " |
| + scale + " value " + toString()); |
| } |
| |
| out.writeByte(scale); |
| out.writeByte(byteArray.length); |
| out.write(byteArray); |
| } |
| |
| /** |
| * Note the use of rawData: we reuse the array if the |
| * incoming array is the same length or smaller than |
| * the array length. |
| * |
| * @see java.io.Externalizable#readExternal |
| */ |
| public void readExternal(ObjectInput in) throws IOException |
| { |
| // clear the previous value to ensure that the |
| // rawData value will be used |
| value = null; |
| |
| rawScale = in.readUnsignedByte(); |
| int size = in.readUnsignedByte(); |
| |
| /* |
| ** Allocate a new array if the data to read |
| ** is larger than the existing array, or if |
| ** we don't have an array yet. |
| |
| Need to use readFully below and NOT just read because read does not |
| guarantee getting size bytes back, whereas readFully does (unless EOF). |
| */ |
| if ((rawData == null) || size != rawData.length) |
| { |
| rawData = new byte[size]; |
| } |
| in.readFully(rawData); |
| |
| } |
| |
| /** |
| * @see Storable#restoreToNull |
| * |
| */ |
| public void restoreToNull() |
| { |
| value = null; |
| rawData = null; |
| } |
| |
| |
| /** @exception StandardException Thrown on error */ |
| protected int typeCompare(DataValueDescriptor arg) throws StandardException |
| { |
| BigDecimal otherValue = SQLDecimal.getBigDecimal(arg); |
| return getBigDecimal().compareTo(otherValue); |
| } |
| |
| /* |
| * DataValueDescriptor interface |
| */ |
| |
| /** |
| * @see DataValueDescriptor#cloneValue |
| */ |
| public DataValueDescriptor cloneValue(boolean forceMaterialization) |
| { |
| return new SQLDecimal(getBigDecimal()); |
| } |
| |
| /** |
| * @see DataValueDescriptor#getNewNull |
| */ |
| public DataValueDescriptor getNewNull() |
| { |
| return new SQLDecimal(); |
| } |
| |
| /** |
| * @see DataValueDescriptor#setValueFromResultSet |
| * |
| * @exception SQLException Thrown on error |
| */ |
| public void setValueFromResultSet(ResultSet resultSet, int colNumber, |
| boolean isNullable) |
| throws SQLException |
| { |
| value = resultSet.getBigDecimal(colNumber); |
| rawData = null; |
| } |
| /** |
| Set the value into a PreparedStatement. |
| |
| @exception SQLException Error setting value in PreparedStatement |
| */ |
| public final void setInto(PreparedStatement ps, int position) throws SQLException { |
| |
| if (isNull()) { |
| ps.setNull(position, java.sql.Types.DECIMAL); |
| return; |
| } |
| |
| ps.setBigDecimal(position, getBigDecimal()); |
| } |
| |
| /** |
| * |
| * <B> WARNING </B> there is no checking to make sure |
| * that theValue doesn't exceed the precision/scale of |
| * the current SQLDecimal. It is just assumed that the |
| * SQLDecimal is supposed to take the precision/scale of |
| * the BigDecimalized String. |
| * |
| * @exception StandardException throws NumberFormatException |
| * when the String format is not recognized. |
| */ |
| public void setValue(String theValue) throws StandardException |
| { |
| rawData = null; |
| |
| if (theValue == null) |
| { |
| value = null; |
| } |
| else |
| { |
| try |
| { |
| theValue = theValue.trim(); |
| value = new BigDecimal(theValue); |
| rawData = null; |
| } catch (NumberFormatException nfe) |
| { |
| throw invalidFormat(); |
| } |
| } |
| } |
| |
| /** |
| * @see NumberDataValue#setValue |
| * |
| * @exception StandardException Thrown on error |
| */ |
| public void setValue(double theValue) throws StandardException |
| { |
| setCoreValue(NumberDataType.normalizeDOUBLE(theValue)); |
| } |
| |
| /** |
| * @see NumberDataValue#setValue |
| * |
| */ |
| public void setValue(float theValue) |
| throws StandardException |
| { |
| setCoreValue((double)NumberDataType.normalizeREAL(theValue)); |
| } |
| |
| /** |
| * @see NumberDataValue#setValue |
| * |
| */ |
| public void setValue(long theValue) |
| { |
| value = BigDecimal.valueOf(theValue); |
| rawData = null; |
| } |
| |
| /** |
| * @see NumberDataValue#setValue |
| * |
| */ |
| public void setValue(int theValue) |
| { |
| setValue((long)theValue); |
| } |
| |
| /** |
| Only to be called when the application sets a value using BigDecimal |
| through setBigDecimal calls. |
| */ |
| public void setBigDecimal(BigDecimal theValue) throws StandardException |
| { |
| setCoreValue(theValue); |
| } |
| |
| /** |
| Called when setting a DECIMAL value internally or from |
| through a procedure or function. |
| Handles long in addition to BigDecimal to handle |
| identity being stored as a long but returned as a DECIMAL. |
| */ |
| public void setValue(Number theValue) throws StandardException |
| { |
| if (SanityManager.ASSERT) |
| { |
| if (theValue != null && |
| !(theValue instanceof java.math.BigDecimal) && |
| !(theValue instanceof java.lang.Long)) |
| SanityManager.THROWASSERT("SQLDecimal.setValue(Number) passed a " + theValue.getClass()); |
| } |
| |
| if (theValue instanceof BigDecimal || theValue == null) |
| setCoreValue((BigDecimal) theValue); |
| else |
| setValue(theValue.longValue()); |
| } |
| |
| /** |
| * @see NumberDataValue#setValue |
| * |
| */ |
| public void setValue(boolean theValue) |
| { |
| setCoreValue(theValue ? BigDecimal.ONE : BigDecimal.ZERO); |
| } |
| |
| /* |
| * DataValueDescriptor interface |
| */ |
| |
| /** @see DataValueDescriptor#typePrecedence */ |
| public int typePrecedence() |
| { |
| return TypeId.DECIMAL_PRECEDENCE; |
| } |
| // END DataValueDescriptor interface |
| |
| private void setCoreValue(BigDecimal theValue) |
| { |
| value = theValue; |
| rawData = null; |
| } |
| |
| private void setCoreValue(double theValue) { |
| value = new BigDecimal(Double.toString(theValue)); |
| rawData = null; |
| } |
| |
| /** |
| * Normalization method - this method may be called when putting |
| * a value into a SQLDecimal, for example, when inserting into a SQLDecimal |
| * column. See NormalizeResultSet in execution. |
| * <p> |
| * Note that truncation is allowed on the decimal portion |
| * of a numeric only. |
| * |
| * @param desiredType The type to normalize the source column to |
| * @param source The value to normalize |
| * |
| * @throws StandardException Thrown for null into |
| * non-nullable column, and for |
| * truncation error |
| */ |
| public void normalize( |
| DataTypeDescriptor desiredType, |
| DataValueDescriptor source) |
| throws StandardException |
| { |
| int desiredScale = desiredType.getScale(); |
| int desiredPrecision = desiredType.getPrecision(); |
| |
| setFrom(source); |
| setWidth(desiredPrecision, desiredScale, true); |
| } |
| |
| |
| /* |
| ** SQL Operators |
| */ |
| |
| |
| /** |
| * This method implements the + operator for DECIMAL. |
| * |
| * @param addend1 One of the addends |
| * @param addend2 The other addend |
| * @param result The result of a previous call to this method, null |
| * if not called yet |
| * |
| * @return A SQLDecimal containing the result of the addition |
| * |
| * @exception StandardException Thrown on error |
| */ |
| |
| public NumberDataValue plus(NumberDataValue addend1, |
| NumberDataValue addend2, |
| NumberDataValue result) |
| throws StandardException |
| { |
| if (result == null) |
| { |
| result = new SQLDecimal(); |
| } |
| |
| if (addend1.isNull() || addend2.isNull()) |
| { |
| result.setToNull(); |
| return result; |
| } |
| |
| result.setBigDecimal(SQLDecimal.getBigDecimal(addend1).add(SQLDecimal.getBigDecimal(addend2))); |
| return result; |
| } |
| |
| /** |
| * This method implements the - operator for "decimal - decimal". |
| * |
| * @param left The value to be subtracted from |
| * @param right The value to be subtracted |
| * @param result The result of a previous call to this method, null |
| * if not called yet |
| * |
| * @return A SQLDecimal containing the result of the subtraction |
| * |
| * @exception StandardException Thrown on error |
| */ |
| |
| public NumberDataValue minus(NumberDataValue left, |
| NumberDataValue right, |
| NumberDataValue result) |
| throws StandardException |
| { |
| if (result == null) |
| { |
| result = new SQLDecimal(); |
| } |
| |
| if (left.isNull() || right.isNull()) |
| { |
| result.setToNull(); |
| return result; |
| } |
| |
| result.setBigDecimal(SQLDecimal.getBigDecimal(left).subtract(SQLDecimal.getBigDecimal(right))); |
| return result; |
| } |
| |
| /** |
| * This method implements the * operator for "double * double". |
| * |
| * @param left The first value to be multiplied |
| * @param right The second value to be multiplied |
| * @param result The result of a previous call to this method, null |
| * if not called yet |
| * |
| * @return A SQLDecimal containing the result of the multiplication |
| * |
| * @exception StandardException Thrown on error |
| */ |
| |
| public NumberDataValue times(NumberDataValue left, |
| NumberDataValue right, |
| NumberDataValue result) |
| throws StandardException |
| { |
| if (result == null) |
| { |
| result = new SQLDecimal(); |
| } |
| |
| if (left.isNull() || right.isNull()) |
| { |
| result.setToNull(); |
| return result; |
| } |
| |
| result.setBigDecimal(SQLDecimal.getBigDecimal(left).multiply(SQLDecimal.getBigDecimal(right))); |
| return result; |
| } |
| |
| /** |
| * This method implements the / operator for BigDecimal/BigDecimal |
| * |
| * @param dividend The numerator |
| * @param divisor The denominator |
| * @param result The result of a previous call to this method, null |
| * if not called yet |
| * |
| * @return A SQLDecimal containing the result of the division |
| * |
| * @exception StandardException Thrown on error |
| */ |
| |
| public NumberDataValue divide(NumberDataValue dividend, |
| NumberDataValue divisor, |
| NumberDataValue result) |
| throws StandardException |
| { |
| return divide(dividend, divisor, result, -1); |
| } |
| |
| /** |
| * This method implements the / operator for BigDecimal/BigDecimal |
| * |
| * @param dividend The numerator |
| * @param divisor The denominator |
| * @param result The result of a previous call to this method, null |
| * if not called yet |
| * @param scale The result scale, if < 0, calculate the scale according |
| * to the actual values' sizes |
| * |
| * @return A SQLDecimal containing the result of the division |
| * |
| * @exception StandardException Thrown on error |
| */ |
| |
| public NumberDataValue divide(NumberDataValue dividend, |
| NumberDataValue divisor, |
| NumberDataValue result, |
| int scale) |
| throws StandardException |
| { |
| if (result == null) |
| { |
| result = new SQLDecimal(); |
| } |
| |
| if (dividend.isNull() || divisor.isNull()) |
| { |
| result.setToNull(); |
| return result; |
| } |
| |
| BigDecimal divisorBigDecimal = SQLDecimal.getBigDecimal(divisor); |
| |
| if (divisorBigDecimal.compareTo(BigDecimal.ZERO) == 0) |
| { |
| throw StandardException.newException(SQLState.LANG_DIVIDE_BY_ZERO); |
| } |
| BigDecimal dividendBigDecimal = SQLDecimal.getBigDecimal(dividend); |
| |
| /* |
| ** Set the result scale to be either the passed in scale, whcih was |
| ** calculated at bind time to be max(ls+rp-rs+1, 4), where ls,rp,rs |
| ** are static data types' sizes, which are predictable and stable |
| ** (for the whole result set column, eg.); otherwise dynamically |
| ** calculates the scale according to actual values. Beetle 3901 |
| */ |
| result.setBigDecimal |
| ( |
| dividendBigDecimal.divide |
| ( |
| divisorBigDecimal, |
| scale > -1 ? |
| scale : |
| Math.max |
| ( |
| (dividendBigDecimal.scale() + SQLDecimal.getWholeDigits(divisorBigDecimal) + 1), |
| NumberDataValue.MIN_DECIMAL_DIVIDE_SCALE |
| ), |
| RoundingMode.DOWN) |
| ); |
| |
| return result; |
| } |
| |
| /** |
| * This method implements the unary minus operator for double. |
| * |
| * @param result The result of a previous call to this method, null |
| * if not called yet |
| * |
| * @return A SQLDecimal containing the result of the division |
| * |
| * @exception StandardException Thrown on error |
| */ |
| |
| public NumberDataValue minus(NumberDataValue result) |
| throws StandardException |
| { |
| if (result == null) |
| { |
| result = new SQLDecimal(); |
| } |
| |
| if (this.isNull()) |
| { |
| result.setToNull(); |
| return result; |
| } |
| |
| result.setBigDecimal(getBigDecimal().negate()); |
| return result; |
| } |
| |
| /** |
| * This method implements the isNegative method. |
| * |
| * @return A boolean. If this.value is negative, return true. |
| * For positive values or null, return false. |
| */ |
| |
| protected boolean isNegative() |
| { |
| return !isNull() && (getBigDecimal().compareTo(BigDecimal.ZERO) == -1); |
| } |
| |
| /* |
| * String display of value |
| */ |
| public String toString() |
| { |
| if (isNull()) |
| return "NULL"; |
| else |
| return getString(); |
| } |
| |
| /* |
| * Hash code |
| */ |
| public int hashCode() |
| { |
| long longVal; |
| BigDecimal localValue = getBigDecimal(); |
| |
| double doubleVal = (localValue != null) ? localValue.doubleValue() : 0; |
| |
| if (Double.isInfinite(doubleVal)) |
| { |
| /* |
| ** This loses the fractional part, but it probably doesn't |
| ** matter for numbers that are big enough to overflow a double - |
| ** it's probably rare for numbers this big to be different only in |
| ** their fractional parts. |
| */ |
| longVal = localValue.longValue(); |
| } |
| else |
| { |
| longVal = (long) doubleVal; |
| if (longVal != doubleVal) |
| { |
| longVal = Double.doubleToLongBits(doubleVal); |
| } |
| } |
| |
| return (int) (longVal ^ (longVal >> 32)); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // |
| // VariableSizeDataValue interface |
| // |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Set the precision/scale of the to the desired values. |
| * Used when CASTing. Ideally we'd recycle normalize(), but |
| * the use is different. |
| * |
| * @param desiredPrecision the desired precision -- IGNORE_PREICISION |
| * if it is to be ignored. |
| * @param desiredScale the desired scale |
| * @param errorOnTrunc throw error on truncation (ignored -- |
| * always thrown if we truncate the non-decimal part of |
| * the value) |
| * |
| * @exception StandardException Thrown on non-zero truncation |
| * if errorOnTrunc is true |
| */ |
| public void setWidth(int desiredPrecision, |
| int desiredScale, |
| boolean errorOnTrunc) |
| throws StandardException |
| { |
| if (isNull()) |
| return; |
| |
| if (desiredPrecision != IGNORE_PRECISION && |
| ((desiredPrecision - desiredScale) < SQLDecimal.getWholeDigits(getBigDecimal()))) |
| { |
| throw StandardException.newException(SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, |
| ("DECIMAL/NUMERIC("+desiredPrecision+","+desiredScale+")")); |
| } |
| value = value.setScale(desiredScale, RoundingMode.DOWN); |
| rawData = null; |
| } |
| |
| /** |
| * Return the SQL scale of this value, number of digits after the |
| * decimal point, or zero for a whole number. This does not match the |
| * return from BigDecimal.scale() since in J2SE 5.0 onwards that can return |
| * negative scales. |
| */ |
| public int getDecimalValuePrecision() |
| { |
| if (isNull()) |
| return 0; |
| |
| BigDecimal localValue = getBigDecimal(); |
| |
| return SQLDecimal.getWholeDigits(localValue) + getDecimalValueScale(); |
| } |
| |
| /** |
| * Return the SQL scale of this value, number of digits after the |
| * decimal point, or zero for a whole number. This does not match the |
| * return from BigDecimal.scale() since in J2SE 5.0 onwards that can return |
| * negative scales. |
| */ |
| public int getDecimalValueScale() |
| { |
| if (isNull()) |
| return 0; |
| |
| if (value == null) |
| return rawScale; |
| |
| int scale = value.scale(); |
| if (scale >= 0) |
| return scale; |
| |
| // BigDecimal scale is negative, so number must have no fractional |
| // part as its value is the unscaled value * 10^-scale |
| return 0; |
| } |
| |
| /** |
| * Get a BigDecimal representing the value of a DataValueDescriptor |
| * @param value Non-null value to be converted |
| * @return BigDecimal value |
| * @throws StandardException Invalid conversion or out of range. |
| */ |
| public static BigDecimal getBigDecimal(DataValueDescriptor value) throws StandardException |
| { |
| if (SanityManager.DEBUG) |
| { |
| if (value.isNull()) |
| SanityManager.THROWASSERT("NULL value passed to SQLDecimal.getBigDecimal"); |
| } |
| |
| switch (value.typeToBigDecimal()) |
| { |
| case Types.DECIMAL: |
| return (BigDecimal) value.getObject(); |
| case Types.CHAR: |
| try { |
| return new BigDecimal(value.getString().trim()); |
| } catch (NumberFormatException nfe) { |
| throw StandardException.newException(SQLState.LANG_FORMAT_EXCEPTION, "java.math.BigDecimal"); |
| } |
| case Types.BIGINT: |
| return BigDecimal.valueOf(value.getLong()); |
| default: |
| if (SanityManager.DEBUG) |
| SanityManager.THROWASSERT("invalid return from " + value.getClass() + ".typeToBigDecimal() " + value.typeToBigDecimal()); |
| return null; |
| } |
| } |
| |
| /** |
| * Calculate the number of digits to the left of the decimal point |
| * of the passed in value. |
| * @param decimalValue Value to get whole digits from, never null. |
| * @return number of whole digits. |
| */ |
| private static int getWholeDigits(BigDecimal decimalValue) |
| { |
| /** |
| * if ONE > abs(value) then the number of whole digits is 0 |
| */ |
| decimalValue = decimalValue.abs(); |
| if (BigDecimal.ONE.compareTo(decimalValue) == 1) |
| { |
| return 0; |
| } |
| |
| // precision is the number of digits in the unscaled value, |
| // subtracting the scale (positive or negative) will give the |
| // number of whole digits. |
| return decimalValue.precision() - decimalValue.scale(); |
| } |
| } |