How to convert from EBCDIC to ASCII in C#.net
Asked Answered
H

7

10

I have a value in EBCDIC format "000000{". I want to convert it into a.Net Int32 type. Can anyone let me know what I can do about it?? So my question is given a string that contains a signed numeric in EBCDIC , what should I be doing to convert it into a .NET Int32.

Thanks so much in advance!

Harumscarum answered 18/5, 2010 at 14:33 Comment(1)
Pretty bad title; this question is completely unrelated to ASCII.Sigil
C
25

Try this

    #region public static byte[] ConvertAsciiToEbcdic(byte[] asciiData)
    public static byte[] ConvertAsciiToEbcdic(byte[] asciiData)     
    {          
        // Create two different encodings.         
        Encoding ascii = Encoding.ASCII;
        Encoding ebcdic = Encoding.GetEncoding("IBM037");          

        //Retutn Ebcdic Data
        return Encoding.Convert(ascii, ebcdic, asciiData);      
    }     
    #endregion      

    #region public static byte[] ConvertEbcdicToAscii(byte[] ebcdicData)
    public static byte[] ConvertEbcdicToAscii(byte[] ebcdicData)
    {         
        // Create two different encodings.      
        Encoding ascii = Encoding.ASCII;
        Encoding ebcdic = Encoding.GetEncoding("IBM037"); 

        //Retutn Ascii Data 
        return Encoding.Convert(ebcdic, ascii, ebcdicData); 
    } 
    #endregion
Celia answered 8/2, 2011 at 22:34 Comment(1)
Downvoted because the question was related to an EBCDIC over-punch numeric value, not EBCDIC encoded text. The answer provided by Simon is the correct handling for these fields.Xi
M
5

The following program has worked for converting an EBCDIC value to an integer, when receiving data from one of our customers. The data we get may be a subset of what you might get, so see if this works for you:

using System;
using System.Text;

namespace ConsoleApplication6
{
    class Program
    {
        static void Main(string[] args)
        {
            string strAmount = "00007570{";
            Console.WriteLine("{0} is {1}", strAmount, ConvertEBCDICtoInt(strAmount));
            strAmount = "000033}";
            Console.WriteLine("{0} is {1}", strAmount, ConvertEBCDICtoInt(strAmount));
            Console.ReadLine();
        }

        // This converts "00007570{" into "75700", and "000033}" into "-330"
        public static int? ConvertEBCDICtoInt(string i_strAmount)
        {
            int? nAmount = null;

            if (string.IsNullOrEmpty(i_strAmount))
                return(nAmount);

            StringBuilder strAmount = new StringBuilder(i_strAmount);
            if (i_strAmount.IndexOfAny(new char[] { '}', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R' }) >= 0)
                strAmount.Insert(0, "-");

            strAmount.Replace("{", "0");
            strAmount.Replace("}", "0");
            strAmount.Replace("A", "1");
            strAmount.Replace("J", "1");
            strAmount.Replace("B", "2");
            strAmount.Replace("K", "2");
            strAmount.Replace("C", "3");
            strAmount.Replace("L", "3");
            strAmount.Replace("D", "4");
            strAmount.Replace("M", "4");
            strAmount.Replace("E", "5");
            strAmount.Replace("N", "5");
            strAmount.Replace("F", "6");
            strAmount.Replace("O", "6");
            strAmount.Replace("G", "7");
            strAmount.Replace("P", "7");
            strAmount.Replace("H", "8");
            strAmount.Replace("Q", "8");
            strAmount.Replace("I", "9");
            strAmount.Replace("R", "9");

            // Convert the amount to a int:
            int n;
            if (int.TryParse(strAmount.ToString(), out n))
                nAmount = n;
            return (nAmount);
        }
    }
}
Mccleary answered 18/5, 2010 at 16:45 Comment(1)
This solution actually handles EBCDIC over-punch values.Xi
M
3

You're going to want to read up on binary-coded decimals, as that is what you are facing, and there are questions to answer before you can really code it.

If the value is a single character, it may be as simple as getting the char number--but you need to know if the system is Big Endian (like most mainframes from which you would be getting EBDIC-encoded files) or Little Endian (like more modern OSes).

If your integer value uses more than one character and includes the sign (as you mention), then it is more complex. Most likely, each half (or "nibble", or 4 bits) of each character represents the number--maybe 0 thru 9 or in hex 0 thru F, and the string is padded with zeros (nulls, actually) on the left, and the last nibble contains the sign. This system might be called Zoned Decimal in some parlance.

All in all, I would recommend starting by reading this article, which should introduce you to how data is/was stored on COBOL-based mainframes, and get you moving the right direction.


In C#, you may be able to do the conversion form the common Zoned Decimal (which sounds like the best fit for your incoming data as you have described it) by using int.Parse with the correct NumberStyles options, like this:

int val = int.Parse(num, NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite);
Mcintire answered 18/5, 2010 at 14:53 Comment(1)
(BTW, I'm still looking for example code in C#, since the examples I have are all in Java.)Mcintire
O
1

Generally speaking, you should be able to load EBCDIC data using the correct System.Text.Encoding class (the link points to a list of all encodings, which includes EBCDIC encodings). The string is then Unicode in memory and can be saved to ASCII using the ASCII encoding.

This does what you ask for in the title of the question. However, I'm not sure whether this is what you wanted to know, since your question isn't fully clear to me. If you're looking for the ASCII character code, you can just cast the character to an int as long as they are ASCII characters only.

Overwrite answered 18/5, 2010 at 14:37 Comment(3)
Your suggestion would be okay and quite correct for character data, but alas not at all good for numerical data, since it would change the numbers with the new character mapping.Mcintire
@ewall, thanks for the clarification. I'm not familiar with EBCDIC itself, but I knew that there were encodings for it in the framework. That's also why I wrote the bottom paragraph about not being sure whether this actually answers the question at hand. As long as the title is left unchanged I'll leave my answer here for others which are searching for EBCDIC character conversion and come across this question, but I'll be happy to delete it when the title and question text are clearer.Overwrite
Indeed, it is a great answer for converting between the character encodings.Mcintire
N
1

These are the extension methods and unit test that we use:

    /// <summary>
    /// parses a signed or unsigned decimal in EBCDIC format int an integer
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    private static int? FromZonedDecimalString(this string value)
    {
        var trimmed = ("" + value).Trim();
        if (trimmed.Length == 0)
            return null;

        int testValue;
        if (Int32.TryParse(trimmed, out testValue))
            return testValue;

        var lastChar = Convert.ToChar(trimmed.Substring(trimmed.Length - 1, 1));
        var result = 0;

        if (trimmed.Length > 1)
            result = Int32.Parse(trimmed.Substring(0, trimmed.Length - 1)) * 10;

        switch (lastChar)
        {
            case '{':
                return result;
            case '}':
                return -1 * result;
            default:
                if (lastChar >= 'A' && lastChar <= 'I')
                    return result + lastChar - 'A' + 1;
                if (lastChar >= 'J' && lastChar <= 'R')
                    return (result + lastChar - 'J' + 1) * -1;
                if (lastChar >= '0' && lastChar <= '9')
                    return (result + lastChar - '0' + 1) * -1;
                break;
        }
        return null;
    }

    /// <summary>
    /// converts an integer value into zoned signed EBCDIC decimal format
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public static string ToZonedSignedDecimalString(this int value)
    {
        var str = Math.Abs(value).ToString();
        str = str.Substring(0, str.Length - 1);
        var lastDigit = Math.Abs(value % 10);

        if (value < 0)
        {
            if (lastDigit == 0) return str + "}";
            if (lastDigit == 1) return str + "J";
            if (lastDigit == 2) return str + "K";
            if (lastDigit == 3) return str + "L";
            if (lastDigit == 4) return str + "M";
            if (lastDigit == 5) return str + "N";
            if (lastDigit == 6) return str + "O";
            if (lastDigit == 7) return str + "P";
            if (lastDigit == 8) return str + "Q";
            if (lastDigit == 9) return str + "R";

            throw new NotSupportedException("If this throws, I'm at a loss. Last digit was: " + lastDigit);
        }

        if (lastDigit == 0) return str + "{";
        if (lastDigit == 1) return str + "A";
        if (lastDigit == 2) return str + "B";
        if (lastDigit == 3) return str + "C";
        if (lastDigit == 4) return str + "D";
        if (lastDigit == 5) return str + "E";
        if (lastDigit == 6) return str + "F";
        if (lastDigit == 7) return str + "G";
        if (lastDigit == 8) return str + "H";
        if (lastDigit == 9) return str + "I";

        throw new NotSupportedException("If this throws, I'm at a loss. Last digit was: " + lastDigit);
    }


[TestClass]
public class IntExtensionsTests
{
    [TestMethod]
    public void TestConversion()
    {
        string signedDecimalString;
        int convertedlValue;
        for (int i = -1000001; i <= 1000001; i++)
        {
            signedDecimalString = i.ToZonedSignedDecimalString();
            convertedlValue = signedDecimalString.ConvertRightSignedJustifySignedValueToInt();

            Assert.AreEqual(i, convertedlValue);
        }
    }
}
Newlywed answered 8/7, 2016 at 17:26 Comment(0)
M
1

This question is quite old, but we recently ran into the same issue. It seems that some large financial institutions (I'm looking at you Fidelity) still use old-school mainframe systems that you need to communicate with and these systems expect zoned-decimal.

The problem that I have found with other answers is that they use string manipulation operations, which are slow. I put together a simple C# library that does the conversion numerically and dropped it on GitHub. Check the link below for an in-depth description of the issue. I have included the (current as of now) implementation of the ZonedDecimalConverter class.

zoned-decimal on GitHub

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

namespace ZonedDecimal
{
    public static class ZonedDecimalConverter
    {
        public enum RoundingOperation { AwayFromZero, ToEven, Truncate };

        const byte MASK_UNSIGNED = 0xF0;
        const byte MASK_POSITIVE = 0xC0;
        const byte MASK_NEGATIVE = 0xD0;

        // this is a subset of the IBM code page 37 EBCDIC character set. we are only concerned with the characters that correspond to numbers.
        // for this dictionary, you key in with the code and get the character
        static readonly Dictionary<byte, char> m_IBM037Characters = new Dictionary<byte, char>()
        {   
            {0xC0, '{'},{0xC1, 'A'},{0xC2, 'B'},{0xC3, 'C'},{0xC4, 'D'},{0xC5, 'E'},{0xC6, 'F'},{0xC7, 'G'},{0xC8, 'H'},{0xC9, 'I'}
            ,{0xD0, '}'},{0xD1, 'J'},{0xD2, 'K'},{0xD3, 'L'},{0xD4, 'M'},{0xD5, 'N'},{0xD6, 'O'},{0xD7, 'P'},{0xD8, 'Q'},{0xD9, 'R'}
            ,{0xF0, '0'},{0xF1, '1'},{0xF2, '2'},{0xF3, '3'},{0xF4, '4'},{0xF5, '5'},{0xF6, '6'},{0xF7, '7'},{0xF8, '8'},{0xF9, '9'}
        };

        // this is the inverse of the dictionary above. you key in with the character and get the code
        static readonly Dictionary<char, byte> m_IBM037Codes = m_IBM037Characters.ToDictionary((pair) => pair.Value, (pair) => pair.Key);

        /// <summary>
        /// Returns a string that represents the zone-decimal version of the value specified
        /// </summary>
        /// <param name="value">The value</param>
        /// <param name="digitsLeft">How many fixed digits should be to the left of the decimal place</param>
        /// <param name="digitsRight">How many fixed digits should be to the right of the decimal place</param>
        /// <param name="roundingOperation">Indicates how to handle decimal digits beyond those specified by digitsRight</param>
        /// <returns></returns>
        public static string GetZonedDecimal(decimal value, int digitsLeft, int digitsRight, RoundingOperation roundingOperation)
        {
            // bounds checking
            if (digitsLeft < 1) throw new ArgumentException("Value must be greater than zero.", "digitsLeft");
            if (digitsRight < 0) throw new ArgumentException("Value must be greater than or equal to zero.", "digitsRight");

            // zoned-decimal has its own way of signaling negative
            bool isNegative = false;
            if (value < 0)
            {
                isNegative = true;
                value = -value; // same result as Math.Abs
            }

            // apply any rounding operation
            if (roundingOperation != RoundingOperation.Truncate)
                value = Math.Round(value, digitsRight, roundingOperation == RoundingOperation.AwayFromZero ? MidpointRounding.AwayFromZero : MidpointRounding.ToEven);


            /*   calculating with decimal is extremely slow comapred to int. we'll multiple the number by digitsRight to put all significant
             *   digits into whole number places and then load it into an unsigned long. since ulong.MaxValue is 18446744073709551615,
             *   this gives us 20 digits total to work with. assuming you used 4 digits to the right, you could have up to 16 to the left, etc.
             *   we do not use uint here since uint.MaxValue is 4294967295 and that would only give us 10 digits to work with. many fields
             *   that i have seen have a COBOL signature of S9(11)V99, which is 13 digits total. also, we use unsigned because the sign bit
             *   is not used (zoned-decimal has it own way of signaling negative) and long.MaxValue (vs ulong.MaxValue) is one digit shorter.
             *   if the value is too big to be represented as a ulong with an implied decimal place (not likely) then you're out of luck and 
             *   you'll get an exception here
             */
            ulong workingValue = (ulong)(value * (int)Math.Pow(10, digitsRight));

            // the total number of digits that will be output
            int length = digitsLeft + digitsRight;

            // more bounds checking (e.g. digitsLeft = 3; digitsRight = 2; if number with implied decimal place > 10^5-1=99999 then it will not fit)
            if (workingValue > Math.Pow(10, length) - 1)
                throw new ArgumentException("Value exceeds specified total number of fixed digits.", "value");

            // each character will be a digit of the number
            char[] output = new char[length];

            // loop through the number and output each digit as zoned-decimal
            for (int i = 0; i < length; i++)
            {
                byte digit = 0;

                // if we run out of digits then we'll just keep looping, padding the specified fixed number
                // of decimal places with zeros
                if (workingValue > 0)
                {
                    // current digit is the one that occupies the right-most place
                    digit = (byte)(workingValue % 10);
                    // shift all values to the right, dropping the current right-most value in the process
                    workingValue /= 10;
                }

                // the sign indicator is included in the initial right-most digit only
                if (i == 0)
                    digit |= isNegative ? MASK_NEGATIVE : MASK_POSITIVE;
                else
                    digit |= MASK_UNSIGNED;

                // set values of our character array from right to left based on the IBM code page 37 EBCDIC character set
                output[length - i - 1] = m_IBM037Characters[digit];
            }

            return new string(output);
        }

        /// <summary>
        /// Returns a decimal from a zoned-decimal
        /// </summary>
        /// <param name="zonedDecimalString">The zoned-decimal string</param>
        /// <param name="digitsRight">Number of digits that should be to the right of the decimal place</param>
        /// <returns></returns>
        public static decimal GetDecimal(string zonedDecimalString, int digitsRight)
        {   
            // we'll do most calculations with ulong since it's significantly faster then calculating with decimal
            ulong value = 0;
            // we'll need a way to determine if the number is negative. this will be signaled in the zone of the right-most character
            bool isNegative = false;
            // this will be used to create the place value of each digit
            ulong multipler = 1;
            // start at the right-hand side of the number and proceed to the left
            int lastIndex = zonedDecimalString.Length - 1;
            for (int i = lastIndex; i >= 0; i--)
            {   
                // get the EBCDIC code for the character at position i
                if (!m_IBM037Codes.TryGetValue(zonedDecimalString[i], out byte digit))
                    throw new ArgumentException("Invalid numeric character found in zoned-decimal string", "zonedDecimalString");

                // the right-most character will carry the sign
                if (i == lastIndex)
                    isNegative = (digit & 0xF0) == MASK_NEGATIVE;

                // strip out the zone
                digit &= 0x0F;
                // add the place value of the digit to our return value
                value += digit * multipler;
                // multipler goes to the next "place" (tens/hundreds/thousands/etc)
                multipler *= 10;
            }

            // now we're going to deal with decimal places and negatives, so we have to switch to a decimal
            decimal returnValue = value;

            // deal with digits to the right of the decimal
            if (digitsRight > 0)
                returnValue /= (int)Math.Pow(10, digitsRight);

            // deal with negative
            if (isNegative)
                returnValue = -returnValue;

            return returnValue;
        }
    }
}
Myrtlemyrvyn answered 21/8, 2018 at 18:50 Comment(0)
M
0

Try following function..

public string ConvertEBCDICtoASCII(string strEBCDICString) {
    int[] e2a = new int[256]{
        0, 1, 2, 3,156, 9,134,127,151,141,142, 11, 12, 13, 14, 15,
        16, 17, 18, 19,157,133, 8,135, 24, 25,146,143, 28, 29, 30, 31,
        128,129,130,131,132, 10, 23, 27,136,137,138,139,140, 5, 6, 7,
        144,145, 22,147,148,149,150, 4,152,153,154,155, 20, 21,158, 26,
        32,160,161,162,163,164,165,166,167,168, 91, 46, 60, 40, 43, 33,
        38,169,170,171,172,173,174,175,176,177, 93, 36, 42, 41, 59, 94,
        45, 47,178,179,180,181,182,183,184,185,124, 44, 37, 95, 62, 63,
        186,187,188,189,190,191,192,193,194, 96, 58, 35, 64, 39, 61, 34,
        195, 97, 98, 99,100,101,102,103,104,105,196,197,198,199,200,201,
        202,106,107,108,109,110,111,112,113,114,203,204,205,206,207,208,
        209,126,115,116,117,118,119,120,121,122,210,211,212,213,214,215,
        216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,
        123, 65, 66, 67, 68, 69, 70, 71, 72, 73,232,233,234,235,236,237,
        125, 74, 75, 76, 77, 78, 79, 80, 81, 82,238,239,240,241,242,243,
        92,159, 83, 84, 85, 86, 87, 88, 89, 90,244,245,246,247,248,249,
        48, 49, 50, 51, 52, 53, 54, 55, 56, 57,250,251,252,253,254,255};

    char chrItem = Convert.ToChar("0");
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < strEBCDICString.Length; i++) {
        try {
            chrItem = Convert.ToChar(strEBCDICString.Substring(i, 1));
            sb.Append(Convert.ToChar(e2a[(int)chrItem]));
        } catch (Exception ex) {
            Console.WriteLine(ex.Message);
            return string.Empty;
        }

    }
    string result = sb.ToString();
    sb = null;
    return result;
}
Mihrab answered 18/5, 2010 at 14:40 Comment(1)
No, that won't work. If you read the question more carefully, Sai needs to get numerical data (an integer) out of the byte array. Converting it from EBDIC to ASCII would change the numbers.Mcintire

© 2022 - 2024 — McMap. All rights reserved.