0

I am seeking code to read a text file which is in packed decimal (Comp -3) numeric value which was created in main frames system and is 8 characters, but holds a 13 digit number in packed decimal format.

I came across below code

private Decimal Unpack(byte[] inp, int scale)
{
    long lo = 0;
    long mid = 0;
    long hi = 0;
    bool isNegative;

    // this nybble stores only the sign, not a digit.  
    // "C" hex is positive, "D" hex is negative, and "F" hex is unsigned. 
    switch (nibble(inp, 0))
    {
        case 0x0D:
            isNegative = true;
            break;
        case 0x0F:
        case 0x0C:
            isNegative = false;
            break;
        default:
            throw new Exception("Bad sign nibble");
    }
    long intermediate;
    long carry;
    long digit;
    for (int j = inp.Length * 2 - 1; j > 0; j--)
    {
        // multiply by 10
        intermediate = lo * 10;
        lo = intermediate & 0xffffffff;
        carry = intermediate >> 32;
        intermediate = mid * 10 + carry;
        mid = intermediate & 0xffffffff;
        carry = intermediate >> 32;
        intermediate = hi * 10 + carry;
        hi = intermediate & 0xffffffff;
        carry = intermediate >> 32;
        // By limiting input length to 14, we ensure overflow will never occur

        digit = nibble(inp, j);
        if (digit > 9)
        {
            throw new Exception("Bad digit");
        }
        intermediate = lo + digit;
        lo = intermediate & 0xffffffff;
        carry = intermediate >> 32;
        if (carry > 0)
        {
            intermediate = mid + carry;
            mid = intermediate & 0xffffffff;
            carry = intermediate >> 32;
            if (carry > 0)
            {
                intermediate = hi + carry;
                hi = intermediate & 0xffffffff;
                carry = intermediate >> 32;
                // carry should never be non-zero. Back up with validation
            }
        }
    }
    return new Decimal((int)lo, (int)mid, (int)hi, isNegative, (byte)scale);
}

private int nibble(byte[] inp, int nibbleNo)
{
    int b = inp[inp.Length - 1 - nibbleNo / 2];
    return (nibbleNo % 2 == 0) ? (b & 0x0000000F) : (b >> 4);
}

but above code fails saying bad sign nibble.

can anyone confirm if i am reading properly

 using (FileStream fs = new FileStream(pathSource, FileMode.Open))
            {
                using (StreamReader reader = new StreamReader(fs))
                {
                    List<decimal> list = new List<decimal>();

                    while (!reader.EndOfStream)
                    {
                        byte[] b = ASCIIEncoding.ASCII.GetBytes(reader.ReadLine());
                        list.Add(Unpack(b, 0));
                    }

                    reader.Close();
                }
            }

Note: it's not duplicated as I am looking for code that can read a file and pass a parameter to Unpack method.

For reference I added how the data inside the file looks like:

image

halfer
  • 19,824
  • 17
  • 99
  • 186
VR1256
  • 1,266
  • 4
  • 28
  • 56
  • `Encoding.ASCII.GetBytes(reader.ReadLine())` -- that looks suspicious. What does your text file look like, exactly? – canton7 Nov 06 '19 at 14:42
  • I have added the image of how the file looks like in the question – VR1256 Nov 06 '19 at 14:46
  • 2
    [StreamReader](https://learn.microsoft.com/en-us/dotnet/api/system.io.streamreader.-ctor?view=netframework-4.8#System_IO_StreamReader__ctor_System_IO_Stream_System_Text_Encoding_) accepts an Encoding parameter which makes `Encoding.ASCII.GetBytes(reader.ReadLine())` strange - besides, if you want the *bytes*, why use a StreamReader at all? Just use Stream.Read – Panagiotis Kanavos Nov 06 '19 at 14:46
  • 1
    @VijenderReddyChintalapudi why are you using `Encoding.ASCII.GetBytes` at all? In any case, `Encoding.ASCII` is the 7-bit US-ASCII encoding which will discard every value above 128 (x7F). – Panagiotis Kanavos Nov 06 '19 at 14:47
  • I was suspicious on the way i am reading the file, can you help on how to read the file while passing to Unpack method ? (it would be a 13 digit number on each line of the file) – VR1256 Nov 06 '19 at 14:51
  • What program are you using to view the file ???. The fact you are getting lines of 8 bytes suggests the file has undergone a ASCII conversion which will corrupt the Comp-3 values. Try looking at the file using the RecordEditor. – Bruce Martin Nov 07 '19 at 02:30
  • I'm also facing the same issue. Have you found any solution for this issue? – Sathyaraj Palanisamy Jun 02 '20 at 20:08

1 Answers1

1

The “ASCII transfer type” will transfer the files as regular text files. So files becoming corrupt when we transfer packed decimal or binary data files in ASCII transfer type. The “Binary transfer type” will transfer the data in binary mode which handles the files as binary data instead of text data. So please use Binary transfer type for your case.

Reference : https://www.codeproject.com/Tips/673240/EBCDIC-to-ASCII-Converter

Once your file is ready, here is the code to convert packed decimal to human readable decimal.

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

   namespace ConsoleApp2
   {
    class Program
    {
        static void Main(string[] args)
        {
            var path = @"C:\FileName.BIN.dat";
            var templates = new List<Template>
            {
                new Template{StartPos=1,CharLength=4,Type="AlphaNum"},
                new Template{StartPos=5,CharLength=1,Type="AlphaNum"},
                new Template{StartPos=6,CharLength=8,Type="AlphaNum"},
                new Template{StartPos=14,CharLength=1,Type="AlphaNum"},
                new Template{StartPos=46,CharLength=4,Type="Packed",DecimalPlace=2},
                new Template{StartPos=54,CharLength=5,Type="Packed",DecimalPlace=0},
                new Template{StartPos=60,CharLength=4,Type="Packed",DecimalPlace=2},
                new Template{StartPos=64,CharLength=1,Type="AlphaNum"}
            };

            var allBytes = File.ReadAllBytes(path);
            for (int i = 0; i < allBytes.Length; i += 66)
            {
                var IsLastline = (allBytes.Length - i) < 66;
                var lineLength = IsLastline ? 64 : 66;
                byte[] lineBytes = new byte[lineLength];
                Array.Copy(allBytes, i, lineBytes, 0, lineLength);


                var outArray = new string[templates.Count];
                int index = 0;
                foreach (var temp in templates)
                {
                    byte[] amoutBytes = new byte[temp.CharLength];
                    Array.Copy(lineBytes, temp.StartPos - 1, amoutBytes, 0, 
    temp.CharLength);
                    var final = "";
                    if (temp.Type == "Packed")
                    {
                        final = Unpack(amoutBytes, temp.DecimalPlace).ToString();
                    }
                    else
                    {
                        final = ConvertEbcdicString(amoutBytes);
                    }

                    outArray[index] = final;
                    index++;

                }

                Console.WriteLine(string.Join(" ", outArray));

            }

            Console.ReadLine();
        }


        private static string ConvertEbcdicString(byte[] ebcdicBytes)
        {
            if (ebcdicBytes.All(p => p == 0x00 || p == 0xFF))
            {
                //Every byte is either 0x00 or 0xFF (fillers)
                return string.Empty;
            }

            Encoding ebcdicEnc = Encoding.GetEncoding("IBM037");
            string result = ebcdicEnc.GetString(ebcdicBytes); // convert EBCDIC Bytes -> 
    Unicode string
            return result;
        }

        private static Decimal Unpack(byte[] inp, int scale)
        {
            long lo = 0;
            long mid = 0;
            long hi = 0;
            bool isNegative;

            // this nybble stores only the sign, not a digit.  
            // "C" hex is positive, "D" hex is negative, AlphaNumd "F" hex is unsigned. 
            var ff = nibble(inp, 0);
            switch (ff)
            {
                case 0x0D:
                    isNegative = true;
                    break;
                case 0x0F:
                case 0x0C:
                    isNegative = false;
                    break;
                default:
                    throw new Exception("Bad sign nibble");
            }
            long intermediate;
            long carry;
            long digit;
            for (int j = inp.Length * 2 - 1; j > 0; j--)
            {
                // multiply by 10
                intermediate = lo * 10;
                lo = intermediate & 0xffffffff;
                carry = intermediate >> 32;
                intermediate = mid * 10 + carry;
                mid = intermediate & 0xffffffff;
                carry = intermediate >> 32;
                intermediate = hi * 10 + carry;
                hi = intermediate & 0xffffffff;
                carry = intermediate >> 32;
                // By limiting input length to 14, we ensure overflow will never occur

                digit = nibble(inp, j);
                if (digit > 9)
                {
                    throw new Exception("Bad digit");
                }
                intermediate = lo + digit;
                lo = intermediate & 0xffffffff;
                carry = intermediate >> 32;
                if (carry > 0)
                {
                    intermediate = mid + carry;
                    mid = intermediate & 0xffffffff;
                    carry = intermediate >> 32;
                    if (carry > 0)
                    {
                        intermediate = hi + carry;
                        hi = intermediate & 0xffffffff;
                        carry = intermediate >> 32;
                        // carry should never be non-zero. Back up with validation
                    }
                }
            }
            return new Decimal((int)lo, (int)mid, (int)hi, isNegative, (byte)scale);
        }

        private static int nibble(byte[] inp, int nibbleNo)
        {
            int b = inp[inp.Length - 1 - nibbleNo / 2];
            return (nibbleNo % 2 == 0) ? (b & 0x0000000F) : (b >> 4);
        }

        class Template
        {
            public string Name { get; set; }
            public string Type { get; set; }
            public int StartPos { get; set; }
            public int CharLength { get; set; }
            public int DecimalPlace { get; set; }
        }
    }
   }