How to convert from EBCDIC to ASCII in C#.net

2020-02-01 01:26发布

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!

标签: c# ascii ebcdic
7条回答
贼婆χ
2楼-- · 2020-02-01 02:12

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;
}
查看更多
▲ chillily
3楼-- · 2020-02-01 02:16

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;
        }
    }
}
查看更多
不美不萌又怎样
4楼-- · 2020-02-01 02:17

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);
        }
    }
}
查看更多
趁早两清
5楼-- · 2020-02-01 02:18

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.

查看更多
成全新的幸福
6楼-- · 2020-02-01 02:25

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);
查看更多
Explosion°爆炸
7楼-- · 2020-02-01 02:26

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
查看更多
登录 后发表回答