Java: Convert String to packed decimal

19,054

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

Share:
19,054
mpmp
Author by

mpmp

Updated on June 20, 2022

Comments

  • mpmp
    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
    mpmp about 12 years
    Some 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
    mpmp about 12 years
    I will try it out. Thank you for your response :)
  • mpmp
    mpmp about 12 years
    Oh, 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
    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
    HaMi over 9 years
    Thanks Dodge. How about converting Decimal numbers (with float values) into COBOL byte array and vice verse?
  • Dodge
    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.
    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
    Gilbert Le Blanc over 8 years
    @jr: You're correct. I don't remember what I was thinking 3 years ago.