如何在C#.net中将EBCDIC转换为ASCII

10

我手上有一个以EBCDIC格式表示的值"000000{",我想将其转换为.Net Int32类型。请问我该怎么做?我的问题是,如果给出一个包含带符号数字的字符串,我应该如何将其转换为.NET Int32。

非常感谢您提前的帮助!


2
标题不太好,这个问题与ASCII完全无关。 - Nyerguds
7个回答

25

试试这个

    #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

2
因为问题涉及到EBCDIC超级数字值,而不是EBCDIC编码文本,所以被投了反对票。Simon提供的答案是处理这些字段的正确方法。 - lukevp

5
以下程序已经成功将EBCDIC值转换为整数,当我们从客户那里接收数据时使用。我们得到的数据可能是你所得到数据的子集,因此请看看这是否适用于你:
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);
        }
    }
}

1
此解决方案实际上处理了EBCDIC过度打孔值。 - lukevp

3

您需要了解二进制编码十进制,因为这是您所面临的问题,并且在编写代码之前需要回答一些问题。

如果该值是单个字符,则获取字符编号可能很简单 - 但您需要知道系统是大端(例如大多数主机从中获取EBDIC编码文件)还是小端(例如更现代的操作系统)。

如果您的整数值使用多个字符并包括符号(如您所提到的),则情况更加复杂。最有可能的是,每个字符的每半部分(或“nibble”或4位)表示数字 - 可能是0到9或十六进制中的0到F,并且字符串在左侧填充了零(实际上是空值),而最后一个nibble包含符号。在某些术语中,此系统可能称为区域性十进制

总的来说,我建议先阅读这篇文章,它会介绍COBOL主机上数据是/如何存储的,并帮助你朝着正确的方向前进。
在C#中,你可以使用 int.Parse和正确的NumberStyles选项来进行通用分区十进制转换(根据你描述的数据,这似乎是最合适的选择),示例代码如下:
int val = int.Parse(num, NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite);

顺便提一句,我仍在寻找C#的示例代码,因为我手头的示例都是Java的。 - ewall

1
这个问题很旧,但我们最近遇到了同样的问题。似乎一些大型金融机构(我看着你,富达)仍在使用老式的主机系统,您需要与这些系统通信,并且这些系统期望带区域的十进制数。
我发现其他答案存在的问题是它们使用字符串操作,速度较慢。我编写了一个简单的C#库,通过数字转换进行转换,并将其放在了GitHub上。请查看以下链接以获取有关此问题的详细说明。我已包含ZonedDecimalConverter类的实现(截至目前为止)。 GitHub上的zoned-decimal
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;
        }
    }
}

1

这些是我们使用的扩展方法和单元测试:

    /// <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);
        }
    }
}

1

一般来说,您应该能够使用正确的System.Text.Encoding类(链接指向包括EBCDIC编码在内的所有编码列表)加载EBCDIC数据。 在内存中,字符串就是Unicode格式,可以使用ASCII编码将其保存为ASCII。

这样做符合您在问题标题中提出的要求。 但是,我不确定这是否是您想要了解的内容,因为您的问题对我来说不是完全清楚的。 如果您正在寻找ASCII字符代码,只要它们仅为ASCII字符,就可以将字符强制转换为int


您的建议对于字符数据可以接受并且相当正确,但是对于数字数据来说却不好,因为它会通过新的字符映射改变数字。 - ewall
@ewall,感谢澄清。我本人不熟悉EBCDIC本身,但我知道框架中有其编码方式。这也是为什么我写了最后一段关于不确定这是否真正回答了问题的原因。只要标题不改变,我将把我的答案保留在这里供其他想寻找EBCDIC字符转换的人参考,但当标题和问题文本更清晰时,我很乐意删除它。 - Lucero
确实,这是一个很好的答案,可以在字符编码之间进行转换。 - ewall

0

尝试以下函数...

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;
}

1
不,那样行不通。如果你仔细阅读问题,Sai 需要从字节数组中获取数值数据(一个整数)。将其从 EBDIC 转换为 ASCII 将改变数字的值。 - ewall

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接