src.com.ibm.as400.access.AS400ZonedDecimal Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jt400-jdk8 Show documentation
Show all versions of jt400-jdk8 Show documentation
The Open Source version of the IBM Toolbox for Java
///////////////////////////////////////////////////////////////////////////////
//
// JTOpen (IBM Toolbox for Java - OSS version)
//
// Filename: AS400ZonedDecimal.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;
/**
* The AS400ZonedDecimal class provides a converter between a BigDecimal object and a zoned decimal format floating point number.
**/
public class AS400ZonedDecimal implements AS400DataType
{
static final long serialVersionUID = 4L;
private int digits;
private int scale;
private static final long defaultValue = 0;
private static final boolean HIGH_NIBBLE = AS400PackedDecimal.HIGH_NIBBLE;
private static final boolean LOW_NIBBLE = AS400PackedDecimal.LOW_NIBBLE;
private boolean useDouble_ = false;
/**
* Constructs an AS400ZonedDecimal object.
* @param numDigits The number of digits in the zoned 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 zoned decimal number. It must be greater than or equal to zero and less than or equal to numDigits.
*/
public AS400ZonedDecimal(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 AS400ZonedDecimal 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,e);
}
}
/**
* 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;
}
/**
* 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_ZONED TYPE_ZONED}.
* @return AS400DataType.TYPE_ZONED.
**/
public int getInstanceType()
{
return AS400DataType.TYPE_ZONED;
}
/**
* 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 zoned decimal number.
* @return The number of digits.
**/
public int getNumberOfDigits()
{
return this.digits;
}
/**
* Returns the number of decimal positions in the zoned 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.AS400PackedDecimal#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];
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 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.
* @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;
// verify input
BigDecimal inValue = (BigDecimal)javaValue; // Let this line throw ClassCastException
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[]
// write correct number of leading zero's
for (int i=0; i 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!
// If the effective scale is different than the actual scale,
// then pad with zeros.
int rightmostOffset = offset + digits - 1;
int padOffset = rightmostOffset - (scale - effectiveScale);
for (int i = rightmostOffset; i > padOffset; --i)
as400Value[i] = (byte)0x00F0;
// Compute the bytes for the right side of the decimal point.
int decimalOffset = rightmostOffset - scale;
int nextDigit;
for (int i = padOffset; i > decimalOffset; --i) {
nextDigit = (int)(rightSide % 10);
as400Value[i] = (byte)(0x00F0 | nextDigit);
rightSide /= 10;
}
// Compute the bytes for the left side of the decimal point.
for (int i = decimalOffset; i >= offset; --i) {
nextDigit = (int)(leftSide % 10);
as400Value[i] = (byte)(0x00F0 | nextDigit);
leftSide /= 10;
}
// Fix the sign, if negative.
if (doubleValue < 0)
as400Value[rightmostOffset] = (byte)(as400Value[rightmostOffset] & 0x00DF);
// 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 digits;
}
// @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.
/*
* This old code had a bug in that it can produce
* inexact answers. For example
* 10.10105 is turned into -10.101049999999999
double doubleValue = 0;
double multiplier = Math.pow(10, digits - scale - 1);
int rightMostOffset = offset + digits - 1;
for(int i = offset; i <= rightMostOffset; ++i) {
doubleValue += ((byte)(as400Value[i] & 0x000F)) * multiplier;
multiplier /= 10;
}
*/
/*
* Instead we gather the digits using a long, then divide by the scale.
* Note: Using a multiply by Math.pow(10, -scale) gives a worse answer.
* Math.pow(10,-scale) is a less accurate number than Math.pow(10,scale)
*/
int rightMostOffset = offset + digits - 1;
double doubleValue = 0;
if(digits < 19){
long longValue = 0;
double divisor = Math.pow(10, scale);
for(int i = offset; i <= rightMostOffset; ++i) {
longValue = longValue * 10 + (byte)(as400Value[i] & 0x000F);
}
doubleValue = longValue / divisor;
} else {
double divisor = Math.pow(10, scale);
for(int i = offset; i <= rightMostOffset; ++i) {
doubleValue = doubleValue * 10 + (byte)(as400Value[i] & 0x000F);
}
doubleValue = doubleValue / divisor;
}
// Determine the sign.
switch(as400Value[rightMostOffset] & 0x00F0) {
case 0x00B0:
case 0x00D0:
// Negative.
doubleValue *= -1;
break;
case 0x00A0:
case 0x00C0:
case 0x00E0:
case 0x00F0:
// Positive.
break;
default:
throwNumberFormatException(HIGH_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.
* @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)
{
if (useDouble_) return new Double(toDouble(as400Value, offset));
// Check offset to prevent bogus NumberFormatException message
if (offset < 0)
{
throw new ArrayIndexOutOfBoundsException(String.valueOf(offset));
}
int size = this.digits;
int outputPosition = 0; // position in char[]
int digitsPlaced = 0; // number of digits moved from input to output
char[] outputData = null;
// read the sign bit, allow ArrayIndexException to be thrown
int nibble = (as400Value[offset+size-1] & 0xFF) >>> 4;
switch (nibble)
{
case 0x000B: // valid negative sign bits
case 0x000D:
outputData = new char[size+1];
outputData[outputPosition++] = '-';
break;
case 0x000A: // valid positive sign bits
case 0x000C:
case 0x000E:
case 0x000F:
outputData = new char[size];
break;
default: // others invalid
{
throwNumberFormatException(HIGH_NIBBLE, offset+size-1,
as400Value[offset+size-1] & 0xFF,
as400Value);
return null; // return
}
}
// place the digits
while (outputPosition < outputData.length)
{
nibble = as400Value[offset++] & 0x000F;
if (nibble > 0x0009) {
if (Trace.traceOn_) Trace.log(Trace.ERROR,
" outputPosition="+outputPosition+
" outputData.length="+outputData.length +
" offset (after increment)= "+offset);
throwNumberFormatException(LOW_NIBBLE, offset-1,
as400Value[offset-1] & 0x00FF,
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 'zoned 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);
}
}