我有一系列ASCII平面文件从大型机进来,由C#应用程序处理.引入了一个带有Packed Decimal(COMP-3)字段的新Feed,需要将其转换为数值.
使用ASCII传输模式通过FTP传输文件.我担心二进制字段可能包含将被解释为非常低的ASCII代码或控制字符而不是值 - 或者更糟糕的是,可能在FTP进程中丢失.
更重要的是,字段被读作字符串.我可以灵活地解决这个问题(即某种流),但业务会给我带来阻力.
该要求为"从HEX转换为ASCII",但显然没有产生正确的值.任何帮助,将不胜感激; 只要您能够解释转换过程的逻辑,它就不必是特定于语言的.
我一直在观看有关将Comp-3 BCD数据从"传统"大型机文件转换为C#中可用内容的众多板上的帖子.首先,我想说的是,我对其中一些帖子收到的回复很不满意 - 尤其是那些基本上说过"你为什么要在这些非C#/ C++相关帖子中烦扰我们"以及"如果你需要一个关于某种COBOL约定的答案,你为什么不去参观一个面向COBOL的网站".对我来说,这是完整的BS,因为很可能需要很多年(不幸的是),软件开发人员需要了解如何处理现实世界中存在的一些遗留问题.所以,即使我在下面的代码中抨击了这篇文章,我将与你分享一个关于COMP-3/EBCDIC转换我必须处理的真实世界经验(是的,我是他所说的"软盘,纸带,光盘包等...... - 自1979年以来我一直是软件工程师.
首先 - 了解您从像IBM这样的传统主框架系统中读取的任何文件将以EBCDIC格式向您呈现数据,并且为了将任何数据转换为您可以处理的C#/ C++字符串,必须使用正确的代码页转换才能将数据转换为ASCII格式.如何处理这个的一个很好的例子是:
StreamReader readFile = new StreamReader(path,Encoding.GetEncoding(037); // 037 = EBCDIC到ASCII转换.
这将确保您从此流中读取的任何内容将转换为ASCII并可以以字符串格式使用.这包括COBOL声明的"Zoned Decimal"(图9)和"Text"(Pic X)字段.但是,当读入char []或byte []数组时,这不一定将COMP-3字段转换为正确的"二进制"等效项.要做到这一点,你唯一能够正确翻译(甚至使用UTF-8,UTF-16,默认或其他)代码页的方法,你将要打开这样的文件:
FileStream fileStream = new FileStream(path,FIleMode.Open,FIleAccess.Read,FileShare.Read);
当然,"FileShare.Read"选项是"可选的".
当您将要转换为十进制值的字段(然后在需要时随后转换为ASCII字符串)时,您可以使用以下代码 - 这基本上是从MicroSoft"UnpackDecimal"发布的可以到达:
http://www.microsoft.com/downloads/details.aspx?familyid=0e4bba52-cc52-4d89-8590-cda297ff7fbd&displaylang=en
我已经孤立(我认为)这个逻辑中最重要的部分是什么,并将其合并为两个方法,您可以使用您想要的方法.出于我的目的,我选择将其保留为返回十进制值,然后我可以根据需要进行操作.基本上,该方法称为"unpack",您将其传递给byte []数组(不超过12个字节),并将比例作为int传递,这是您希望在Decimal值中返回的小数位数.我希望这对你有用,对我也有用.
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); }
如果您有任何疑问,请将它们发布在这里 - 因为我怀疑我会像其他所有选择发布与当前问题相关的问题的人一样"受到抨击"......
谢谢,约翰 - 长老.
首先,您必须消除由ASCII传输模式引起的行尾(EOL)转换问题.当BCD值恰好与EOL字符对应时,您完全担心数据损坏.这个问题的最糟糕的方面是它很少会出乎意料地发生.
最佳解决方案是将传输模式更改为BIN.这是合适的,因为您传输的数据是二进制的.如果无法使用正确的FTP传输模式,则可以撤消代码中的ASCII模式损坏.您所要做的就是将\ r \n对转换回\n.如果我是你,我会确保这是经过充分测试的.
一旦你处理了EOL问题,COMP-3转换就非常困难了.我能够在MS知识库中找到这篇文章,其中包含BASIC中的示例代码.请参阅下面的代码的VB.NET端口.
由于您正在处理COMP-3值,因此您正在阅读的文件格式几乎肯定具有固定字段长度的固定记录大小.如果我是你,我会先了解一下文件格式规范,然后再继续讨论.您应该使用BinaryReader来处理这些数据.如果有人正在推翻这一点,我会走开.让他们找别人放纵他们的愚蠢.
这是BASIC示例代码的VB.NET端口.我没有测试过这个,因为我无法访问COMP-3文件.如果这不起作用,我会回顾原始的MS示例代码以获取指导,或参考此问题的其他答案中的参考.
Imports Microsoft.VisualBasic Module Module1 'Sample COMP-3 conversion code 'Adapted from http://support.microsoft.com/kb/65323 'This code has not been tested Sub Main() Dim Digits%(15) 'Holds the digits for each number (max = 16). Dim Basiceqv#(1000) 'Holds the Basic equivalent of each COMP-3 number. 'Added to make code compile Dim MyByte As Char, HighPower%, HighNibble% Dim LowNibble%, Digit%, E%, Decimal%, FileName$ 'Clear the screen, get the filename and the amount of decimal places 'desired for each number, and open the file for sequential input: FileName$ = InputBox("Enter the COBOL data file name: ") Decimal% = InputBox("Enter the number of decimal places desired: ") FileOpen(1, FileName$, OpenMode.Binary) Do Until EOF(1) 'Loop until the end of the file is reached. Input(1, MyByte) If MyByte = Chr(0) Then 'Check if byte is 0 (ASC won't work on 0). Digits%(HighPower%) = 0 'Make next two digits 0. Increment Digits%(HighPower% + 1) = 0 'the high power to reflect the HighPower% = HighPower% + 2 'number of digits in the number 'plus 1. Else HighNibble% = Asc(MyByte) \ 16 'Extract the high and low LowNibble% = Asc(MyByte) And &HF 'nibbles from the byte. The Digits%(HighPower%) = HighNibble% 'high nibble will always be a 'digit. If LowNibble% <= 9 Then 'If low nibble is a 'digit, assign it and Digits%(HighPower% + 1) = LowNibble% 'increment the high HighPower% = HighPower% + 2 'power accordingly. Else HighPower% = HighPower% + 1 'Low nibble was not a digit but a Digit% = 0 '+ or - signals end of number. 'Start at the highest power of 10 for the number and multiply 'each digit by the power of 10 place it occupies. For Power% = (HighPower% - 1) To 0 Step -1 Basiceqv#(E%) = Basiceqv#(E%) + (Digits%(Digit%) * (10 ^ Power%)) Digit% = Digit% + 1 Next 'If the sign read was negative, make the number negative. If LowNibble% = 13 Then Basiceqv#(E%) = Basiceqv#(E%) - (2 * Basiceqv#(E%)) End If 'Give the number the desired amount of decimal places, print 'the number, increment E% to point to the next number to be 'converted, and reinitialize the highest power. Basiceqv#(E%) = Basiceqv#(E%) / (10 ^ Decimal%) Print(Basiceqv#(E%)) E% = E% + 1 HighPower% = 0 End If End If Loop FileClose() 'Close the COBOL data file, and end. End Sub End Module