我有一个 EBCDIC 格式的值“000000{”。我想把它转换成.Net Int32 类型。谁能让我知道我能做些什么?所以我的问题是在 EBCDIC 中给出了一个包含有符号数字的字符串,我应该怎么做才能将它转换为 .NET Int32。
提前非常感谢!
尝试这个
#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
在从我们的一位客户处接收数据时,以下程序可将 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);
}
}
}
您将要阅读二进制编码的小数,因为这就是您所面临的,并且在您真正编码之前有一些问题需要回答。
如果该值是单个字符,它可能就像获取字符数一样简单——但您需要知道系统是 Big Endian(就像大多数大型机一样,您将从其中获取 EBDIC 编码文件)还是 Little Endian(像更现代的操作系统)。
如果您的整数值使用多个字符并包含符号(如您所述),那么它会更复杂。最有可能的是,每个字符的每一半(或“半字节”,或 4 位)代表数字——可能是 0 到 9 或十六进制 0 到 F,并且字符串在左侧用零(实际上是空值)填充,最后一个半字节包含符号。用某种说法,这个系统可能被称为分区十进制。
总而言之,我建议从阅读这篇文章开始,它应该向您介绍数据是如何存储在基于 COBOL 的大型机上的,并让您朝着正确的方向前进。
在 C# 中,您可以通过使用带有正确 NumberStyles 选项的 int.Parse来转换常见的 Zoned Decimal(这听起来最适合您所描述的传入数据) ,如下所示:
int val = int.Parse(num, NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite);
一般来说,您应该能够使用正确的System.Text.Encoding类加载 EBCDIC 数据(链接指向所有编码的列表,其中包括 EBCDIC 编码)。然后字符串在内存中是 Unicode,可以使用 ASCII 编码保存为 ASCII。
这可以满足您在问题标题中的要求。但是,我不确定这是否是您想知道的,因为您的问题对我来说并不完全清楚。如果您正在寻找 ASCII 字符代码,只要它们只是 ASCII 字符,您就可以将字符转换为 a int
。
这些是我们使用的扩展方法和单元测试:
/// <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);
}
}
}
这个问题很老了,但我们最近遇到了同样的问题。似乎一些大型金融机构(我在看你的 Fidelity)仍然使用你需要与之通信的老式大型机系统,并且这些系统期望分区十进制。
我在其他答案中发现的问题是它们使用了很慢的字符串操作操作。我整理了一个简单的 C# 库,它以数字方式进行转换并将其放在 GitHub 上。检查下面的链接以获取对该问题的深入描述。我已经包含了 ZonedDecimalConverter 类的(截至目前的)实现。
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;
}
}
}
尝试以下功能..
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;
}