src.com.ibm.as400.access.AS400PackedDecimal Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jt400-jdk9 Show documentation
Show all versions of jt400-jdk9 Show documentation
The Open Source version of the IBM Toolbox for Java
///////////////////////////////////////////////////////////////////////////////
//
// JTOpen (IBM Toolbox for Java - OSS version)
//
// Filename: AS400PackedDecimal.java
//
// The source code contained herein is licensed under the IBM Public License
// Version 1.0, which has been approved by the Open Source Initiative.
// Copyright (C) 1997-2004 International Business Machines Corporation and
// others. All rights reserved.
//
///////////////////////////////////////////////////////////////////////////////
package com.ibm.as400.access;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* Provides a converter between a BigDecimal object and a packed decimal format floating point number.
**/
public class AS400PackedDecimal implements AS400DataType
{
static final long serialVersionUID = 4L;
private int digits_;
private int scale_;
private static final long defaultValue = 0;
static final boolean HIGH_NIBBLE = true;
static final boolean LOW_NIBBLE = false;
private boolean useDouble_ = false;
/**
* Constructs an AS400PackedDecimal object.
* @param numDigits The number of digits in the packed decimal number. It must be greater than or equal to one and less than or equal to thirty-one.
* @param numDecimalPositions The number of decimal positions in the packed decimal number. It must be greater than or equal to zero and less than or equal to numDigits.
**/
public AS400PackedDecimal(int numDigits, int numDecimalPositions)
{
// check for valid input
if (numDigits < 1 || numDigits > 63) // @M0C - changed the upper limit here from 31 for JDBC support
{
throw new ExtendedIllegalArgumentException("numDigits (" + String.valueOf(numDigits) + ")", ExtendedIllegalArgumentException.RANGE_NOT_VALID);
}
if (numDecimalPositions < 0 || numDecimalPositions > numDigits)
{
throw new ExtendedIllegalArgumentException("numDecimalPositions (" + String.valueOf(numDecimalPositions) + ")", ExtendedIllegalArgumentException.RANGE_NOT_VALID);
}
// set instance variables
this.digits_ = numDigits;
this.scale_ = numDecimalPositions;
}
/**
* Creates a new AS400PackedDecimal object that is identical to the current instance.
* @return The new object.
**/
public Object clone()
{
try
{
return super.clone(); // Object.clone does not throw exception
}
catch (CloneNotSupportedException e)
{
Trace.log(Trace.ERROR, "Unexpected cloning error", e);
throw new InternalErrorException(InternalErrorException.UNKNOWN);
}
}
/**
* Returns the byte length of the data type.
* @return The number of bytes in the IBM i representation of the data type.
**/
public int getByteLength()
{
return this.digits_/2+1;
}
/**
* Returns a Java object representing the default value of the data type.
* @return The BigDecimal object with a value of zero.
**/
public Object getDefaultValue()
{
return BigDecimal.valueOf(defaultValue);
}
/**
* Returns {@link com.ibm.as400.access.AS400DataType#TYPE_PACKED TYPE_PACKED}.
* @return AS400DataType.TYPE_PACKED.
**/
public int getInstanceType()
{
return AS400DataType.TYPE_PACKED;
}
/**
* Returns the Java class that corresponds with this data type.
* @return BigDecimal.class.
**/
public Class getJavaType()
{
return BigDecimal.class;
}
/**
* Returns the total number of digits in the packed decimal number.
* @return The number of digits.
**/
public int getNumberOfDigits()
{
return this.digits_;
}
/**
* Returns the number of decimal positions in the packed decimal number.
* @return The number of decimal positions.
**/
public int getNumberOfDecimalPositions()
{
return this.scale_;
}
/**
* Indicates if a {@link java.lang.Double Double} object or a
* {@link java.math.BigDecimal BigDecimal} object will be returned
* on a call to {@link #toObject toObject()}.
* @return true if a Double will be returned, false if a BigDecimal
* will be returned. The default is false.
**/
public boolean isUseDouble()
{
return useDouble_;
}
/**
* Sets whether to return a {@link java.lang.Double Double} object or a
* {@link java.math.BigDecimal BigDecimal} object on a call to
* {@link #toObject toObject()}.
* @param b
* @see com.ibm.as400.access.AS400ZonedDecimal#setUseDouble
**/
public void setUseDouble(boolean b)
{
useDouble_ = b;
}
/**
* Converts the specified Java object to IBM i format.
* @param javaValue The object corresponding to the data type. It must be an instance of BigDecimal and the BigDecimal must have a less than or equal to number of digits and a less than or equal to number of decimal places.
* @return The IBM i representation of the data type.
**/
public byte[] toBytes(Object javaValue)
{
byte[] as400Value = new byte[this.digits_/2+1];
this.toBytes(javaValue, as400Value, 0);
return as400Value;
}
/**
* Converts the specified Java object into IBM i format in the specified byte array.
* @param javaValue The object corresponding to the data type. It must be an instance of BigDecimal and the BigDecimal must have a less than or equal to number of digits and a less than or equal to number of decimal places.
* @param as400Value The array to receive the data type in IBM i format. There must be enough space to hold the IBM i value.
* @return The number of bytes in the IBM i representation of the data type.
**/
public int toBytes(Object javaValue, byte[] as400Value)
{
return this.toBytes(javaValue, as400Value, 0);
}
/**
* Converts the specified Java object into IBM i format in the specified byte array.
* @param javaValue An object corresponding to the data type. It must be an instance of BigDecimal and the BigDecimal must have a less than or equal to number of digits and a less than or equal to number of decimal places.
* @param as400Value The array to receive the data type in IBM i format. There must be enough space to hold the IBM i value.
* @param offset The offset into the byte array for the start of the IBM i value. It must be greater than or equal to zero.
* @return The number of bytes in the IBM i representation of the data type.
**/
public int toBytes(Object javaValue, byte[] as400Value, int offset)
{
int outDigits = this.digits_;
int outDecimalPlaces = this.scale_;
int outLength = outDigits/2+1;
// verify input
BigDecimal inValue = null;
try {
inValue = (BigDecimal)javaValue; // Let this line throw ClassCastException
}
catch (ClassCastException e) {
Trace.log(Trace.ERROR, "ClassCastException when attempting to cast a " + javaValue.getClass().getName() + " to a BigDecimal", e);
throw e;
}
if (inValue.scale() > outDecimalPlaces) // Let this line throw NullPointerException
{
throw new ExtendedIllegalArgumentException("javaValue (" + javaValue.toString() + ")", ExtendedIllegalArgumentException.LENGTH_NOT_VALID);
}
// read the sign
int sign = inValue.signum();
// get just the digits from BigDecimal, "normalize" away sign, decimal place etc.
char[] inChars = inValue.abs().movePointRight(outDecimalPlaces).toBigInteger().toString().toCharArray();
// Check overall length
int inLength = inChars.length;
if (inLength > outDigits)
{
throw new ExtendedIllegalArgumentException("javaValue (" + javaValue.toString() + ")", ExtendedIllegalArgumentException.LENGTH_NOT_VALID);
}
int inPosition = 0; // position in char[]
// calculate number of leading zero's
int leadingZeros = (outDigits % 2 == 0) ? (outDigits - inLength + 1) : (outDigits - inLength);
// write correct number of leading zero's, allow ArrayIndexException to be thrown below
for (int i=0; i 0)
{
if (leadingZeros % 2 != 0)
{
as400Value[offset++] = (byte)(inChars[inPosition++] & 0x000F);
}
}
else if (Trace.traceOn_)
{
Trace.log(Trace.DIAGNOSTIC, "The calculated number of leading zeros is negative.", leadingZeros);
}
int firstNibble;
int secondNibble;
// place all the digits except last one
while (inPosition < inChars.length-1)
{
firstNibble = (inChars[inPosition++] & 0x000F) << 4;
secondNibble = inChars[inPosition++] & 0x000F;
as400Value[offset++] = (byte)(firstNibble + secondNibble);
}
// place last digit and sign nibble
firstNibble = (inChars[inPosition++] & 0x000F) << 4;
if (sign != -1)
{
as400Value[offset++] = (byte)(firstNibble + 0x000F);
}
else
{
as400Value[offset++] = (byte)(firstNibble + 0x000D);
}
return outLength;
}
// @E0A
/**
* Converts the specified Java object to IBM i format.
*
* @param doubleValue The value to be converted to IBM i format. If the decimal part
* of this value needs to be truncated, it will be rounded towards
* zero. If the integral part of this value needs to be truncated,
* an exception will be thrown.
* @return The IBM i representation of the data type.
**/
public byte[] toBytes(double doubleValue)
{
byte[] as400Value = new byte[digits_/2+1];
toBytes(doubleValue, as400Value, 0);
return as400Value;
}
// @E0A
/**
* Converts the specified Java object into IBM i format in
* the specified byte array.
*
* @param doubleValue The value to be converted to IBM i format. If the decimal part
* of this value needs to be truncated, it will be rounded towards
* zero. If the integral part of this value needs to be truncated,
* an exception will be thrown.
* @param as400Value The array to receive the data type in IBM i format. There must
* be enough space to hold the IBM i value.
* @return The number of bytes in the IBM i representation of the data type.
**/
public int toBytes(double doubleValue, byte[] as400Value)
{
return toBytes(doubleValue, as400Value, 0);
}
// @E0A
/**
* Converts the specified Java object into IBM i format in
* the specified byte array.
*
* @param doubleValue The value to be converted to IBM i format. If the decimal part
* of this value needs to be truncated, it will be rounded towards
* zero. If the integral part of this value needs to be truncated,
* an exception will be thrown.
* @param as400Value The array to receive the data type in IBM i format.
* There must be enough space to hold the IBM i value.
* @param offset The offset into the byte array for the start of the IBM i value.
* It must be greater than or equal to zero.
* @return The number of bytes in the IBM i representation of the data type.
**/
public int toBytes(double doubleValue, byte[] as400Value, int offset)
{
// GOAL: For performance reasons, we need to do this conversion
// without creating any Java objects (e.g., BigDecimals,
// Strings).
// If the number is too big, we can't do anything with it.
double absValue = Math.abs(doubleValue);
if (absValue > Long.MAX_VALUE)
throw new ExtendedIllegalArgumentException("doubleValue", ExtendedIllegalArgumentException.LENGTH_NOT_VALID);
// Extract the normalized value. This is the value represented by
// two longs (one for each side of the decimal point). Using longs
// here improves the quality of the algorithm as well as the
// performance of arithmetic operations. We may need to use an
// "effective" scale due to the lack of precision representable
// by a long.
long leftSide = (long)absValue;
int effectiveScale = (scale_ > 15) ? 15 : scale_;
long rightSide = (long)Math.round((absValue - (double)leftSide) * Math.pow(10, effectiveScale));
// Ok, now we are done with any double arithmetic!
int length = digits_/2;
int b = offset + length;
boolean nibble = true; // true for left nibble, false for right nibble.
// If the effective scale is different than the actual scale,
// then pad with zeros.
int scaleDifference = scale_ - effectiveScale;
for (int i = 1; i <= scaleDifference; ++i) {
if (nibble) {
as400Value[b] &= (byte)(0x000F);
--b;
}
else {
as400Value[b] &= (byte)(0x00F0);
}
nibble = !nibble;
}
// Compute the bytes for the right side of the decimal point.
int nextDigit;
for (int i = 1; i <= effectiveScale; ++i) {
nextDigit = (int)(rightSide % 10);
if (nibble) {
as400Value[b] &= (byte)(0x000F);
as400Value[b] |= ((byte)nextDigit << 4);
--b;
}
else {
as400Value[b] &= (byte)(0x00F0);
as400Value[b] |= (byte)nextDigit;
}
nibble = !nibble;
rightSide /= 10;
}
// Compute the bytes for the left side of the decimal point.
int leftSideDigits = digits_ - scale_;
for (int i = 1; i <= leftSideDigits; ++i) {
nextDigit = (int)(leftSide % 10);
if (nibble) {
as400Value[b] &= (byte)(0x000F);
as400Value[b] |= ((byte)nextDigit << 4);
--b;
}
else {
as400Value[b] &= (byte)(0x00F0);
as400Value[b] |= (byte)nextDigit;
}
nibble = !nibble;
leftSide /= 10;
}
// Zero out the left part of the value, if needed.
while (b >= offset) {
if (nibble) {
as400Value[b] &= (byte)(0x000F);
--b;
}
else {
as400Value[b] &= (byte)(0x00F0);
}
nibble = !nibble;
}
// Fix the sign.
b = offset + length;
as400Value[b] &= (byte)(0x00F0);
as400Value[b] |= (byte)((doubleValue >= 0) ? 0x000F : 0x000D);
// If left side still has digits, then the value was too big
// to fit.
if (leftSide > 0)
throw new ExtendedIllegalArgumentException("doubleValue", ExtendedIllegalArgumentException.LENGTH_NOT_VALID);
return length+1;
}
// @E0A
/**
* Converts the specified IBM i data type to a Java double value. If the
* decimal part of the value needs to be truncated to be represented by a
* Java double value, then it is rounded towards zero. If the integral
* part of the value needs to be truncated to be represented by a Java
* double value, then it converted to either Double.POSITIVE_INFINITY
* or Double.NEGATIVE_INFINITY.
*
* @param as400Value The array containing the data type in IBM i format.
* The entire data type must be represented.
* @return The Java double value corresponding to the data type.
**/
public double toDouble(byte[] as400Value)
{
return toDouble(as400Value, 0);
}
// @E0A
/**
* Converts the specified IBM i data type to a Java double value. If the
* decimal part of the value needs to be truncated to be represented by a
* Java double value, then it is rounded towards zero. If the integral
* part of the value needs to be truncated to be represented by a Java
* double value, then it converted to either Double.POSITIVE_INFINITY
* or Double.NEGATIVE_INFINITY.
*
* @param as400Value The array containing the data type in IBM i format.
* The entire data type must be represented.
* @param offset The offset into the byte array for the start of the IBM i value.
* It must be greater than or equal to zero.
* @return The Java double value corresponding to the data type.
**/
public double toDouble(byte[] as400Value, int offset)
{
// Check the offset to prevent bogus NumberFormatException message.
if (offset < 0)
throw new ArrayIndexOutOfBoundsException(String.valueOf(offset));
// Compute the value.
double doubleValue = 0;
double multiplier = Math.pow(10, -scale_);
int rightMostOffset = offset + digits_/2;
boolean nibble = true; // true for left nibble, false for right nibble.
for(int i = rightMostOffset; i >= offset;) {
if (nibble) {
doubleValue += (byte)((as400Value[i] & 0x00F0) >> 4) * multiplier;
--i;
}
else {
doubleValue += ((byte)(as400Value[i] & 0x000F)) * multiplier;
}
multiplier *= 10;
nibble = ! nibble;
}
// Determine the sign.
switch(as400Value[rightMostOffset] & 0x000F) {
case 0x000B:
case 0x000D:
// Negative.
doubleValue *= -1;
break;
case 0x000A:
case 0x000C:
case 0x000E:
case 0x000F:
// Positive.
break;
default:
throwNumberFormatException(LOW_NIBBLE, rightMostOffset,
as400Value[rightMostOffset] & 0x00FF,
as400Value);
}
return doubleValue;
}
/**
* Converts the specified IBM i data type to a Java object.
* @param as400Value The array containing the data type in IBM i format. The entire data type must be represented.
* @return The BigDecimal object corresponding to the data type.
**/
public Object toObject(byte[] as400Value) {
return this.toObject(as400Value, 0);
}
/**
* Converts the specified IBM i data type to a Java object.
* @param as400Value The array containing the data type in IBM i format. The entire data type must be represented and the data type must have valid packed decimal format.
* @param offset The offset into the byte array for the start of the IBM i value. It must be greater than or equal to zero.
* @return The BigDecimal object corresponding to the data type.
**/
public Object toObject(byte[] as400Value, int offset) {
return toObject(as400Value, offset, false);
}
public Object toObject(byte[] as400Value, int offset, boolean ignoreErrors) { /*@Q2C*/
int startOffset = offset;
if (useDouble_) return new Double(toDouble(as400Value, offset));
// Check offset to prevent bogus NumberFormatException message
if (offset < 0)
{
if (ignoreErrors) { /*@Q2A*/
return null;
} else {
throw new ArrayIndexOutOfBoundsException(String.valueOf(offset));
}
}
int numDigits = this.digits_;
int inputSize = numDigits/2+1;
// even number of digits will have a leading zero
if (numDigits%2 == 0) ++numDigits;
char[] outputData = null;
int outputPosition = 0; // position in char[]
// read the sign nibble, allow ArrayIndexException to be thrown
int nibble = (as400Value[offset+inputSize-1] & 0x0F);
switch (nibble)
{
case 0x0B: // valid negative sign bits
case 0x0D:
outputData = new char[numDigits+1];
outputData[outputPosition++] = '-';
break;
case 0x0A: // valid positive sign bits
case 0x0C:
case 0x0E:
case 0x0F:
outputData = new char[numDigits];
break;
default: // others invalid
if (ignoreErrors) { /*@Q2A*/
return null;
} else {
throwNumberFormatException(LOW_NIBBLE, offset+inputSize-1,
as400Value[offset+inputSize-1] & 0xFF,
as400Value);
}
return null;
}
// read all the digits except last one
while (outputPosition < (outputData.length-1))
{
nibble = (as400Value[offset] & 0xFF) >>> 4;
if (nibble > 0x09) {
if (ignoreErrors) { /*@Q2A*/
return null;
} else {
throwNumberFormatException(HIGH_NIBBLE, offset,
as400Value[offset] & 0xFF,
as400Value);
}
}
outputData[outputPosition] = (char)(nibble | 0x0030);
outputPosition++;
nibble = (as400Value[offset] & 0x0F);
if (nibble > 0x09) {
if (Trace.traceOn_) Trace.log(Trace.ERROR,
" outputPosition="+outputPosition+
" outputData.length="+outputData.length +
" numDigits = "+numDigits +
" this.digits = "+this.digits_ +
" offset = "+offset+
" startOffset = "+startOffset);
if (ignoreErrors) { /*@Q2A*/
return null;
} else {
throwNumberFormatException(LOW_NIBBLE, offset,
as400Value[offset] & 0xFF,
as400Value);
}
}
offset++;
outputData[outputPosition] = (char)(nibble | 0x0030);
outputPosition++;
}
// read last digit
nibble = (as400Value[offset] & 0xFF) >>> 4;
if (nibble > 0x09) {
if (ignoreErrors) { /*@Q2A*/
return null;
} else {
throwNumberFormatException(HIGH_NIBBLE, offset,
as400Value[offset] & 0xFF,
as400Value);
}
}
outputData[outputPosition] = (char)(nibble | 0x0030);
// construct New BigDecimal object
return new BigDecimal(new BigInteger(new String(outputData)), this.scale_);
}
static final void throwNumberFormatException(boolean highNibble, int byteOffset, int byteValue, byte[] fieldBytes) throws NumberFormatException
{
String text;
if (highNibble) {
text = ResourceBundleLoader.getText("EXC_HIGH_NIBBLE_NOT_VALID", Integer.toString(byteOffset), byteToString(byteValue));
}
else {
text = ResourceBundleLoader.getText("EXC_LOW_NIBBLE_NOT_VALID", Integer.toString(byteOffset), byteToString(byteValue));
}
if (Trace.traceOn_) Trace.log(Trace.ERROR, "Byte sequence is not valid for a field of type 'packed decimal':", fieldBytes);
NumberFormatException nfe = new NumberFormatException(text);
if (Trace.traceOn_) Trace.log(Trace.ERROR, nfe);
throw nfe;
}
private static final String byteToString(int byteVal)
{
int leftDigitValue = (byteVal >>> 4) & 0x0F;
int rightDigitValue = byteVal & 0x0F;
char[] digitChars = new char[2];
// 0x30 = '0', 0x41 = 'A'
digitChars[0] = leftDigitValue < 0x0A ? (char)(0x30 + leftDigitValue) : (char)(leftDigitValue - 0x0A + 0x41);
digitChars[1] = rightDigitValue < 0x0A ? (char)(0x30 + rightDigitValue) : (char)(rightDigitValue - 0x0A + 0x41);
return new String(digitChars);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy