Java: Convert String to packed decimal
Solution 1
Here's my version of a long to packed decimal method.
public class PackedDecimal {
public static byte[] format(long number, int bytes) {
byte[] b = new byte[bytes];
final byte minusSign = 0x0D; // Minus
final byte noSign = 0x0F; // Unsigned
String s = Long.toString(number);
int length = s.length();
boolean isNegative = false;
if (s.charAt(0) == '-') {
isNegative = true;
s = s.substring(1);
length--;
}
int extraBytes = length - bytes + 1;
if (extraBytes < 0) {
// Pad extra byte positions with zero
for (int i = 0; i < -extraBytes; i++) {
b[i] = 0x00;
}
} else if (extraBytes > 0) {
// Truncate the high order digits of the number to fit
s = s.substring(extraBytes);
length -= extraBytes;
extraBytes = 0;
}
// Translate the string digits into bytes
for (int i = 0; i < length; i++) {
String digit = s.substring(i, i + 1);
b[i - extraBytes] = Byte.valueOf(digit);
}
// Add the sign byte
if (isNegative) {
b[bytes - 1] = minusSign;
} else {
b[bytes - 1] = noSign;
}
return b;
}
public static void main(String[] args) {
long number = -456L;
byte[] b = PackedDecimal.format(number, 5);
System.out.println("Number: " + number + ", packed: " + byteToString(b));
number = 0L;
b = PackedDecimal.format(number, 5);
System.out.println("Number: " + number + ", packed: " + byteToString(b));
number = 5823L;
b = PackedDecimal.format(number, 5);
System.out.println("Number: " + number + ", packed: " + byteToString(b));
number = 123456L;
b = PackedDecimal.format(number, 5);
System.out.println("Number: " + number + ", packed: " + byteToString(b));
}
public static String byteToString(byte[] b) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < b.length; i++) {
sb.append("0x");
sb.append(Integer.toHexString((int) b[i]).toUpperCase());
sb.append(" ");
}
return sb.toString();
}
}
And here are the test results.
Number: -456, packed: 0x0 0x4 0x5 0x6 0xD
Number: 0, packed: 0x0 0x0 0x0 0x0 0xF
Number: 5823, packed: 0x5 0x8 0x2 0x3 0xF
Number: 123456, packed: 0x3 0x4 0x5 0x6 0xF
Solution 2
The IBM Toolbox for Java and JTOpen library provides data conversion classes for exactly this purpose.
Solution 3
I came across a similar Problem...
The class form the first post to decode/parse the PackedDecimals worked fine... but the code in Gilbert Le Blancs answer did not produce a valid output.
so i fixed his code...
public class PackedDecimal {
private static final int PlusSign = 0x0C; // Plus sign
private static final int MinusSign = 0x0D; // Minus
private static final int NoSign = 0x0F; // Unsigned
private static final int DropHO = 0xFF; // AND mask to drop HO sign bits
private static final int GetLO = 0x0F; // Get only LO digit
public static long parse(byte[] pdIn) throws Exception {
long val = 0; // Value to return
for (int i = 0; i < pdIn.length; i++) {
int aByte = pdIn[i] & DropHO; // Get next 2 digits & drop sign bits
if (i == pdIn.length - 1) { // last digit?
int digit = aByte >> 4; // First get digit
val = val * 10 + digit;
log("digit=" + digit + ", val=" + val);
int sign = aByte & GetLO; // now get sign
if (sign == MinusSign)
val = -val;
else {
// Do we care if there is an invalid sign?
if (sign != PlusSign && sign != NoSign) {
System.out.println();
for (int x = 0; x < pdIn.length; x++) {
System.out.print(Integer.toString(pdIn[x] & 0x000000ff, 16));
}
System.out.println();
throw new Exception("OC7");
}
}
} else {
int digit = aByte >> 4; // HO first
val = val * 10 + digit;
log("digit=" + digit + ", val=" + val);
digit = aByte & GetLO; // now LO
val = val * 10 + digit;
log("digit=" + digit + ", val=" + val);
}
}
return val;
}
public static byte[] format(long number, int byteCount) {
byte[] bytes = new byte[byteCount];
String data = Long.toString(number);
int length = data.length();
boolean isNegative = false;
if (data.charAt(0) == '-') {
isNegative = true;
data = data.substring(1);
length--;
}
if (length % 2 == 0) {
data = "0" + data;
length++;
}
int neededBytes = (int) (((length + 1) / 2f) + 0.5f);
int extraBytes = neededBytes - byteCount;
if (extraBytes < 0) {
// Pad extra byte positions with zero
for (int i = 0; i < -extraBytes; i++) {
bytes[i] = 0x00;
}
} else if (extraBytes > 0) {
// Truncate the high order digits of the number to fit
data = data.substring(extraBytes);
length -= extraBytes;
extraBytes = 0;
}
// Translate the string digits into bytes
for (int pos = 0; pos <= length - 1; pos++) {
String digit = data.substring(pos, pos + 1);
int now = (pos / 2) - extraBytes;
if (pos % 2 == 0) { // High
bytes[now] = (byte) (Byte.valueOf(digit) << 4);
log("HIGH " + digit);
} else { // Low
bytes[now] = (byte) (bytes[now] | (Byte.valueOf(digit) & 0x0f));
log("LOW " + digit);
}
}
// Add the sign byte
if (isNegative) {
bytes[byteCount - 1] = (byte) (bytes[byteCount - 1] | MinusSign);
} else {
bytes[byteCount - 1] = (byte) (bytes[byteCount - 1] | PlusSign);
}
return bytes;
}
private static void log(String string) {
// System.out.println(string);
}
public static void main(String[] args) throws Exception {
long price;
byte[] format;
price = 44981;
format = PackedDecimal.format(price, 5);
System.out.println("Input: " + price);
System.out.println("Bytes: " + byteToString(format));
System.out.println("Result: " + PackedDecimal.parse(format));
System.out.println("---------");
price = 4498;
format = PackedDecimal.format(price, 4);
System.out.println("Input: " + price);
System.out.println("Bytes: " + byteToString(format));
System.out.println("Result: " + PackedDecimal.parse(format));
System.out.println("---------");
price = 1337;
format = PackedDecimal.format(price, 3);
System.out.println("Input: " + price);
System.out.println("Bytes: " + byteToString(format));
System.out.println("Result: " + PackedDecimal.parse(format));
System.out.println("---------");
}
public static String byteToString(byte[] b) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < b.length; i++) {
int curByte = b[i] & 0xFF;
sb.append("0x");
if (curByte <= 0x0F) {
sb.append("0");
}
sb.append(Integer.toString(curByte, 16));
sb.append(" ");
}
return sb.toString().trim();
}
}
The results:
Input: 44981
Bytes: 0x00 0x00 0x44 0x98 0x1c
Result: 44981
---------
Input: 4498
Bytes: 0x00 0x04 0x49 0x8c
Result: 4498
---------
Input: 1337
Bytes: 0x01 0x33 0x7c
Result: 1337
---------
Have fun & enjoy
mpmp
Updated on June 20, 2022Comments
-
mpmp about 2 years
new here!
Situation: I'm working on a project which needs to communicate with an AS/400 server. My task is to basically handle the requests which will be sent to the AS/400 server. To do this, all the user input should be in EDCDIC bytes.
Problem:
I have managed to convert packed decimals to String with the code below, found on this forum:public class PackedDecimal { public static long parse(byte[] pdIn) throws Exception { // Convert packed decimal to long final int PlusSign = 0x0C; // Plus sign final int MinusSign = 0x0D; // Minus final int NoSign = 0x0F; // Unsigned final int DropHO = 0xFF; // AND mask to drop HO sign bits final int GetLO = 0x0F; // Get only LO digit long val = 0; // Value to return for (int i = 0; i < pdIn.length; i++) { int aByte = pdIn[i] & DropHO; // Get next 2 digits & drop sign bits if (i == pdIn.length - 1) { // last digit? int digit = aByte >> 4; // First get digit val = val * 10 + digit; // System.out.println("digit=" + digit + ", val=" + val); int sign = aByte & GetLO; // now get sign if (sign == MinusSign) val = -val; else { // Do we care if there is an invalid sign? if (sign != PlusSign && sign != NoSign) throw new Exception("OC7"); } } else { int digit = aByte >> 4; // HO first val = val * 10 + digit; // System.out.println("digit=" + digit + ", val=" + val); digit = aByte & GetLO; // now LO val = val * 10 + digit; // System.out.println("digit=" + digit + ", val=" + val); } } return val; } // end parse() // Test the above public static void main(String[] args) throws Exception { byte[] pd = new byte[] { 0x19, 0x2C }; // 192 System.out.println(PackedDecimal.parse(pd)); pd = new byte[] { (byte) 0x98, 0x44, 0x32, 0x3D }; // -9844323 System.out.println(PackedDecimal.parse(pd)); pd = new byte[] { (byte) 0x98, 0x44, 0x32 }; // invalid sign System.out.println(PackedDecimal.parse(pd)); } }
My problem now is I have to convert these String values again to EBCDIC bytes so that the AS/400 server would understand it. I'm planning to do something like constructing a request (raw bytes) using the format specified in the Silverlake documentation. Once the request is built, I plan to manually change values inside that request using a POJO which stores my request (with setters and getters) so I could just go like
request.setField1("Stuff".getBytes(Charset.forName("Cp1047")))
.I don't have that much experience with bits, bytes and nibbles. I hope someone could help me out.
In our code, there's a packed decimal we found which consists of 5 bytes. It goes something like = {00 00 00 00 0F}. I convert this using the method I got from the code above and the value I got was 0. Now, I would like to convert this 0 back to its original form with its original byte size 5.
-
mpmp about 12 yearsSome of the people in my team are already working on this. My group was assigned to do it without the use of JTOpen and IBM toolbox etc. :P
-
mpmp about 12 yearsI will try it out. Thank you for your response :)
-
mpmp about 12 yearsOh, by the way, is this possible without the
int bytes
parameter? I mean, is there anyway I can retrieve the byte array size from the converted String itself? -
Gilbert Le Blanc almost 12 years@Miguel Portugal: You could, but normally packed decimal fields on an IBM computer are of a fixed size, irregardless of the content. A packed decimal field is almost always an even number of bytes.
-
HaMi over 9 yearsThanks Dodge. How about converting Decimal numbers (with float values) into COBOL byte array and vice verse?
-
Dodge over 9 years@HaMi i'm sorry but i have no idea. also i'm out of that topic for a long time now.
-
jt. over 8 years@GilbertLeBlanc: In the byteToString method, the example code uses the notation of 0x plus the integer value of the byte. 0x typically denotes a hexadecimal value, so any value greater than 9 is going to be misleading (e.g. 0x13 and 0x15 in the output is not correct). Changing the second append to sb.append(Integer.toHexString(b[i])); would be more appropriate.
-
Gilbert Le Blanc over 8 years@jr: You're correct. I don't remember what I was thinking 3 years ago.