001: /*
002:
003: Derby - Class org.apache.derby.iapi.types.BigIntegerDecimal
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.iapi.types;
023:
024: import java.math.BigInteger;
025: import java.sql.Types;
026:
027: import org.apache.derby.iapi.error.StandardException;
028: import org.apache.derby.iapi.reference.SQLState;
029: import org.apache.derby.iapi.services.sanity.SanityManager;
030:
031: /**
032: * DECIMAL support using the immutable java.math.BigInteger to perform arithmetic
033: * and conversions. Extends BinaryDecimal to use the base
034: * support of that class. J2ME/CDC/Foundation includes BigInteger.
035: *
036: * A BigInteger is used in calculations etc. to represent the integral unscaled value.
037: * It is simply created from new BigInteger(data2c). No additional instance fields
038: * are used by this class, a possible enhancement would be to keep the BigInteger around
039: * but would require calls from the parent class to reset state etc.
040: */
041:
042: public final class BigIntegerDecimal extends BinaryDecimal {
043: private static final BigInteger TEN = BigInteger.valueOf(10L);
044: private static final BigInteger MAXLONG_PLUS_ONE = BigInteger
045: .valueOf(Long.MAX_VALUE).add(BigInteger.ONE);
046: private static final BigInteger MINLONG_MINUS_ONE = BigInteger
047: .valueOf(Long.MIN_VALUE).subtract(BigInteger.ONE);
048:
049: public BigIntegerDecimal() {
050: }
051:
052: public DataValueDescriptor getNewNull() {
053: return new BigIntegerDecimal();
054: }
055:
056: public long getLong() throws StandardException {
057: if (isNull())
058: return 0L;
059:
060: BigInteger bi = new BigInteger(data2c);
061:
062: // If at any time we see that the value to be scaled down
063: // is within the range for a long, then we are guaranteed
064: // that the scaled down value is within the range for long.
065: boolean rangeOk = false;
066: if ((bi.compareTo(BigIntegerDecimal.MAXLONG_PLUS_ONE) < 0)
067: && (bi.compareTo(BigIntegerDecimal.MINLONG_MINUS_ONE) > 0))
068: rangeOk = true;
069:
070: for (int i = 0; i < sqlScale; i++) {
071: bi = bi.divide(BigIntegerDecimal.TEN);
072: if (rangeOk)
073: continue;
074:
075: if ((bi.compareTo(BigIntegerDecimal.MAXLONG_PLUS_ONE) < 0)
076: && (bi
077: .compareTo(BigIntegerDecimal.MINLONG_MINUS_ONE) > 0))
078: rangeOk = true;
079: }
080:
081: // TODO Range checking
082: if (!rangeOk)
083: throw StandardException.newException(
084: SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE, "BIGINT");
085:
086: return bi.longValue();
087: }
088:
089: public float getFloat() throws StandardException {
090: if (isNull())
091: return 0.0f;
092: return NumberDataType.normalizeREAL(Float
093: .parseFloat(getString()));
094: }
095:
096: public double getDouble() throws StandardException {
097: if (isNull())
098: return 0.0;
099: return NumberDataType.normalizeDOUBLE(Double
100: .parseDouble(getString()));
101: }
102:
103: // 0 or null is false, all else is true
104: public boolean getBoolean() {
105: if (isNull())
106: return false;
107:
108: BigInteger bi = new BigInteger(data2c);
109: return bi.compareTo(java.math.BigInteger.ZERO) != 0;
110: }
111:
112: /**
113: * Set the value from a String, the format is
114: * nnnn
115: *
116: * Scale always set to zero.
117: */
118: public void setValue(String theValue) throws StandardException {
119: if (theValue == null) {
120: restoreToNull();
121: return;
122: }
123:
124: theValue = theValue.trim();
125:
126: int dot = theValue.indexOf('.');
127: int ePosition = theValue.indexOf('e');
128: if (ePosition == -1)
129: ePosition = theValue.indexOf('E');
130:
131: int scale = 0;
132: try {
133: // handle the exponent
134: if (ePosition != -1) {
135: if (dot > ePosition)
136: throw invalidFormat();
137:
138: // Integer.parseInt does not handle a + sign in
139: // front of the number, while the format for the
140: // exponent allows it. Need to strip it off.
141:
142: int expOffset = ePosition + 1;
143:
144: if (expOffset >= theValue.length())
145: throw invalidFormat();
146:
147: if (theValue.charAt(expOffset) == '+') {
148: // strip the plus but must ensure the next character
149: // is not a - sign. Any other invalid sign will be handled
150: // by Integer.parseInt.
151: expOffset++;
152: if (expOffset >= theValue.length())
153: throw invalidFormat();
154: if (theValue.charAt(expOffset) == '-')
155: throw invalidFormat();
156: }
157:
158: String exponent = theValue.substring(expOffset);
159:
160: scale = -1 * Integer.parseInt(exponent);
161: theValue = theValue.substring(0, ePosition);
162: }
163:
164: if (dot != -1) {
165: // remove the dot from the string
166: String leading = theValue.substring(0, dot);
167:
168: scale += (theValue.length() - (dot + 1));
169:
170: theValue = leading.concat(theValue.substring(dot + 1,
171: theValue.length()));
172: }
173:
174: if (scale < 0) {
175: for (int i = scale; i < 0; i++)
176: theValue = theValue.concat("0");
177: scale = 0;
178: }
179:
180: BigInteger bi = new BigInteger(theValue);
181: data2c = bi.toByteArray();
182: sqlScale = scale;
183:
184: } catch (NumberFormatException nfe) {
185: throw invalidFormat();
186: }
187: }
188:
189: /* (non-Javadoc)
190: * @see org.apache.derby.iapi.types.DataValueDescriptor#getString()
191: */
192: public String getString() {
193:
194: if (isNull())
195: return null;
196:
197: String unscaled = new BigInteger(data2c).toString();
198:
199: if (sqlScale == 0)
200: return unscaled;
201:
202: boolean isNegative = this .isNegative();
203:
204: if (sqlScale >= (unscaled.length() - (isNegative ? 1 : 0))) {
205: if (isNegative)
206: unscaled = unscaled.substring(1);
207:
208: String val = isNegative ? "-0." : "0.";
209: for (int i = sqlScale - unscaled.length(); i > 0; i--)
210: val = val.concat("0");
211: return val.concat(unscaled);
212: }
213:
214: String val = unscaled
215: .substring(0, unscaled.length() - sqlScale);
216: val = val.concat(".");
217: return val.concat(unscaled.substring(unscaled.length()
218: - sqlScale, unscaled.length()));
219: }
220:
221: /**
222: * Return the SQL precision of this value.
223: */
224: public int getDecimalValuePrecision() {
225: if (isNull())
226: return 0;
227:
228: BigInteger bi = new BigInteger(data2c);
229:
230: if (BigInteger.ZERO.equals(bi))
231: return 0;
232:
233: int precision = bi.toString().length();
234:
235: if (this .isNegative())
236: precision--;
237:
238: if (precision < sqlScale)
239: return sqlScale;
240:
241: return precision;
242: }
243:
244: /**
245: * Compare two non-null NumberDataValues using DECIMAL arithmetic.
246: */
247: protected int typeCompare(DataValueDescriptor arg)
248: throws StandardException {
249:
250: BigIntegerDecimal obid = getBID(arg);
251:
252: // need to align scales to perform comparisions
253: int tscale = getDecimalValueScale();
254: int oscale = obid.getDecimalValueScale();
255:
256: BigInteger tbi = new BigInteger(data2c);
257: BigInteger obi = new BigInteger(obid.data2c);
258:
259: if (tscale < oscale)
260: tbi = BigIntegerDecimal.rescale(tbi, oscale - tscale);
261: else if (oscale < tscale)
262: obi = BigIntegerDecimal.rescale(obi, tscale - oscale);
263:
264: return tbi.compareTo(obi);
265: }
266:
267: /**
268: * Add two non-null NumberDataValues using DECIMAL arithmetic.
269: * Uses add() to perform the calculation.
270: */
271: public NumberDataValue plusNN(NumberDataValue left,
272: NumberDataValue right, NumberDataValue result)
273: throws StandardException {
274:
275: BinaryDecimal resultBid = (BinaryDecimal) result;
276: if (resultBid == null)
277: resultBid = new BigIntegerDecimal();
278:
279: BigIntegerDecimal lbid = getBID(left);
280: BigIntegerDecimal rbid = getBID(right);
281:
282: // need to align scales to perform plus
283: int lscale = lbid.getDecimalValueScale();
284: int rscale = rbid.getDecimalValueScale();
285:
286: BigInteger bi1 = new BigInteger(lbid.data2c);
287: BigInteger bi2 = new BigInteger(rbid.data2c);
288:
289: int tscale = lscale;
290: if (lscale < rscale) {
291: bi1 = BigIntegerDecimal.rescale(bi1, rscale - lscale);
292: tscale = rscale;
293: } else if (rscale < lscale) {
294: bi2 = BigIntegerDecimal.rescale(bi2, lscale - rscale);
295: }
296:
297: bi1 = bi1.add(bi2);
298:
299: resultBid.data2c = bi1.toByteArray();
300: resultBid.sqlScale = tscale;
301:
302: return resultBid;
303: }
304:
305: /**
306: * Negate the number.
307: * @see org.apache.derby.iapi.types.NumberDataValue#minus(org.apache.derby.iapi.types.NumberDataValue)
308: */
309: public NumberDataValue minus(NumberDataValue result)
310: throws StandardException {
311:
312: if (result == null)
313: result = (NumberDataValue) getNewNull();
314:
315: if (isNull())
316: result.setToNull();
317: else {
318: BinaryDecimal rbd = (BinaryDecimal) result;
319:
320: BigInteger bi = new BigInteger(data2c);
321: // scale remains unchanged.
322: rbd.data2c = bi.negate().toByteArray();
323: rbd.sqlScale = sqlScale;
324:
325: }
326:
327: return result;
328: }
329:
330: /**
331: * Multiple two non-null NumberDataValues using DECIMAL arithmetic.
332: * Uses BigInteger.multipy() to perform the calculation.
333: * Simply multiply the unscaled values and add the scales, proof:
334:
335: <code>
336: left * right
337: = (left_unscaled * 10^-left_scale) * (right_unscaled * 10^-right_scale)
338: = (left_unscaled * 10^-left_scale) * (right_unscaled * 10^-right_scale)
339: = (left_unscaled * right_unscaled) * 10^-(left_scale + right_scale)
340: </code>
341: */
342: public NumberDataValue timesNN(NumberDataValue left,
343: NumberDataValue right, NumberDataValue result)
344: throws StandardException {
345: BigIntegerDecimal resultBid = (BigIntegerDecimal) result;
346: if (resultBid == null)
347: resultBid = new BigIntegerDecimal();
348:
349: BigIntegerDecimal lbid = getBID(left);
350: BigIntegerDecimal rbid = getBID(right);
351:
352: BigInteger lbi = new BigInteger(lbid.data2c);
353: BigInteger rbi = new BigInteger(rbid.data2c);
354:
355: rbi = lbi.multiply(rbi);
356: resultBid.data2c = rbi.toByteArray();
357: resultBid.sqlScale = lbid.getDecimalValueScale()
358: + rbid.getDecimalValueScale();
359:
360: return resultBid;
361: }
362:
363: /**
364: * Divide two non-null NumberDataValues using DECIMAL arithmetic.
365: * Uses divide() to perform the calculation.
366: * Simply multiply the unscaled values and subtract the scales, proof:
367:
368: <code>
369: left / right
370: = (left_unscaled * 10^-left_scale) / (right_unscaled * 10^-right_scale)
371: = (left_unscaled / right_unscaled) * (10^-left_scale / 10^-right_scale)
372: = (left_unscaled / right_unscaled) * (10^-(left_scale-right_scale))
373: </code>
374: */
375: public NumberDataValue divideNN(NumberDataValue left,
376: NumberDataValue right, NumberDataValue result, int scale)
377: throws StandardException {
378: BinaryDecimal resultBid = (BinaryDecimal) result;
379: if (resultBid == null)
380: resultBid = new BigIntegerDecimal();
381:
382: BigIntegerDecimal lbid = getBID(left);
383: BigIntegerDecimal rbid = getBID(right);
384:
385: BigInteger lbi = new BigInteger(lbid.data2c);
386: BigInteger rbi = new BigInteger(rbid.data2c);
387:
388: if (BigInteger.ZERO.equals(rbi))
389: throw StandardException
390: .newException(SQLState.LANG_DIVIDE_BY_ZERO);
391:
392: int lscale = lbid.getDecimalValueScale();
393: int rscale = rbid.getDecimalValueScale();
394:
395: if (scale >= 0) {
396: if (lscale < (scale + rscale)) {
397: lbi = BigIntegerDecimal.rescale(lbi, scale + rscale
398: - lscale);
399: lscale = scale + rscale;
400: }
401: }
402:
403: rbi = lbi.divide(rbi);
404: resultBid.sqlScale = lscale - rscale;
405:
406: if (resultBid.sqlScale < 0) {
407: rbi = BigIntegerDecimal.rescale(rbi, -resultBid.sqlScale);
408: resultBid.sqlScale = 0;
409: }
410:
411: resultBid.data2c = rbi.toByteArray();
412:
413: return resultBid;
414: }
415:
416: public void normalize(DataTypeDescriptor desiredType,
417: DataValueDescriptor source) throws StandardException {
418: int desiredScale = desiredType.getScale();
419: int desiredPrecision = desiredType.getPrecision();
420:
421: setFrom(source);
422: setWidth(desiredPrecision, desiredScale, true);
423: }
424:
425: /* (non-Javadoc)
426: * @see org.apache.derby.iapi.types.VariableSizeDataValue#setWidth(int, int, boolean)
427: */
428: public void setWidth(int desiredPrecision, int desiredScale,
429: boolean errorOnTrunc) throws StandardException {
430: if (isNull())
431: return;
432:
433: int deltaScale = desiredScale - sqlScale;
434: if (desiredPrecision != IGNORE_PRECISION) {
435: int currentPrecision = getDecimalValuePrecision();
436:
437: int futurePrecision = currentPrecision + deltaScale;
438:
439: if (futurePrecision > desiredPrecision)
440: throw StandardException.newException(
441: SQLState.LANG_OUTSIDE_RANGE_FOR_DATATYPE,
442: ("DECIMAL/NUMERIC(" + desiredPrecision + ","
443: + desiredScale + ")"));
444: }
445:
446: if (deltaScale == 0)
447: return;
448:
449: BigInteger bi = new BigInteger(data2c);
450:
451: bi = BigIntegerDecimal.rescale(bi, deltaScale);
452:
453: data2c = bi.toByteArray();
454: sqlScale = desiredScale;
455: }
456:
457: /**
458: * Obtain a BinaryDecimal that represents the passed in value.
459: */
460: private BigIntegerDecimal getBID(DataValueDescriptor value)
461: throws StandardException {
462: switch (value.typeToBigDecimal()) {
463: case Types.DECIMAL:
464: return (BigIntegerDecimal) value;
465: case Types.CHAR: {
466: BigIntegerDecimal bid = new BigIntegerDecimal();
467: bid.setValue(value.getString());
468: return bid;
469: }
470:
471: case Types.BIGINT: {
472: BigIntegerDecimal bid = new BigIntegerDecimal();
473: bid.setValue(value.getLong());
474: return bid;
475: }
476: default:
477: if (SanityManager.DEBUG)
478: SanityManager.THROWASSERT("invalid return from "
479: + value.getClass() + ".typeToBigDecimal() "
480: + value.typeToBigDecimal());
481: return null;
482: }
483: }
484:
485: /**
486: * Rescale a BigInteger, a positive delta means the scale is increased, zero
487: * no change and negative decrease of scale. It is up to the caller to
488: * manage the actual scale of the value, e.g. don't allow the scale to go
489: * negative.
490: *
491: * @param bi
492: * value to be rescaled
493: * @param deltaScale
494: * change of scale
495: * @return rescaled value
496: */
497: private static BigInteger rescale(BigInteger bi, int deltaScale) {
498: if (deltaScale == 0)
499: return bi;
500: if (deltaScale > 0) {
501: // scale increasing, e.g. 10.23 to 10.2300
502: for (int i = 0; i < deltaScale; i++)
503: bi = bi.multiply(BigIntegerDecimal.TEN);
504:
505: } else if (deltaScale < 0) {
506: // scale decreasing, e.g. 10.2345 to 10.23
507: for (int i = deltaScale; i < 0; i++)
508: bi = bi.divide(BigIntegerDecimal.TEN);
509: }
510: return bi;
511: }
512:
513: /*
514: * String display of value
515: */
516: public String toString() {
517: if (isNull())
518: return "NULL";
519: else
520: return getString();
521: }
522: }
|