C#:将 COMP-3 压缩十进制转换为人类可读的值
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/142972/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C#: Convert COMP-3 Packed Decimal to Human-Readable Value
提问by tsilb
I have a series of ASCII flat files coming in from a mainframe to be processed by a C# application. A new feed has been introduced with a Packed Decimal (COMP-3) field, which needs to be converted to a numerical value.
我有一系列来自大型机的 ASCII 平面文件,供 C# 应用程序处理。引入了带有压缩十进制 (COMP-3) 字段的新提要,需要将其转换为数值。
The files are being transferred via FTP, using ASCII transfer mode. I am concerned that the binary field may contain what will be interpreted as very-low ASCII codes or control characters instead of a value - Or worse, may be lost in the FTP process.
文件通过 FTP 传输,使用 ASCII 传输模式。我担心二进制字段可能包含将被解释为非常低的 ASCII 代码或控制字符而不是值的内容 - 或者更糟糕的是,可能会在 FTP 过程中丢失。
What's more, the fields are being read as strings. I may have the flexibility to work around this part (i.e. a stream of some sort), but the business will give me pushback.
更重要的是,这些字段被读取为字符串。我可能可以灵活地处理这部分(即某种流),但业务会给我回击。
The requirement read "Convert from HEX to ASCII", but clearly that didn't yield the correct values. Any help would be appreciated; it need not be language-specific as long as you can explain the logic of the conversion process.
要求为“从十六进制转换为 ASCII”,但显然这并没有产生正确的值。任何帮助,将不胜感激; 只要您能解释转换过程的逻辑,它就不必是特定于语言的。
采纳答案by Paul Keister
First of all you must eliminate the end of line (EOL) translation problems that will be caused by ASCII transfer mode. You are absolutely right to be concerned about data corruption when the BCD values happen to correspond to EOL characters. The worst aspect of this problem is that it will occur rarely and unexpectedly.
首先,您必须消除由 ASCII 传输模式引起的行尾 (EOL) 转换问题。当 BCD 值恰好对应于 EOL 字符时,您担心数据损坏是绝对正确的。这个问题最糟糕的方面是它很少发生并且出乎意料。
The best solution is to change the transfer mode to BIN. This is appropriate since the data you are transferring is binary. If it is not possible to use the correct FTP transfer mode, you can undo the ASCII mode damage in code. All you have to do is convert \r\n pairs back to \n. If I were you I would make sure this is well tested.
最好的解决方案是将传输模式更改为 BIN。这是合适的,因为您传输的数据是二进制的。如果无法使用正确的 FTP 传输模式,则可以在代码中撤消 ASCII 模式损坏。您所要做的就是将 \r\n 对转换回 \n。如果我是你,我会确保这是经过充分测试的。
Once you've dealt with the EOL problem, the COMP-3 conversion is pretty straigtforward. I was able to find this articlein the MS knowledgebase with sample code in BASIC. See below for a VB.NET port of this code.
一旦您处理了 EOL 问题,COMP-3 转换就非常简单。我能够在 MS 知识库中找到这篇文章,其中包含 BASIC 中的示例代码。有关此代码的 VB.NET 端口,请参见下文。
Since you're dealing with COMP-3 values, the file format you're reading almost surely has fixed record sizes with fixed field lengths. If I were you, I would get my hands of a file format specification before you go any further with this. You should be using a BinaryReader to work with this data. If someone is pushing back on this point, I would walk away. Let them find someone else to indulge their folly.
由于您正在处理 COMP-3 值,因此您正在阅读的文件格式几乎肯定具有固定字段长度的固定记录大小。如果我是你,在你进一步研究之前,我会得到一份文件格式规范。您应该使用 BinaryReader 来处理这些数据。如果有人反对这一点,我会走开。让他们找其他人来放纵他们的愚蠢。
Here's a VB.NET port of the BASIC sample code. I haven't tested this because I don't have access to a COMP-3 file. If this doesn't work, I would refer back to the original MS sample code for guidance, or to references in the other answers to this question.
这是 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
回答by Jason Stevenson
I apologize if I am way off base here, but perhaps this code sample I'll paste here could help you. This came from VBRocks...
如果我在这里偏离基地,我深表歉意,但也许我将粘贴在这里的代码示例可以帮助您。这来自 VBRocks...
Imports System
Imports System.IO
Imports System.Text
Imports System.Text.Encoding
'4/20/07 submission includes a line spacing addition when a control character is used:
' The line spacing is calculated off of the 3rd control character.
'
' Also includes the 4/18 modification of determining end of file.
'4/26/07 submission inclues an addition of 6 to the record length when the 4th control
' character is an 8. This is because these records were being truncated.
'Authored by Gary A. Lima, aka. VBRocks
''' <summary>
''' Translates an EBCDIC file to an ASCII file.
''' </summary>
''' <remarks></remarks>
Public Class EBCDIC_to_ASCII_Translator
#Region " Example"
Private Sub Example()
'Set your source file and destination file paths
Dim sSourcePath As String = "c:\Temp\MyEBCDICFile"
Dim sDestinationPath As String = "c:\Temp\TranslatedFile.txt"
Dim trans As New EBCDIC_to_ASCII_Translator()
'If your EBCDIC file uses Control records to determine the length of a record, then this to True
trans.UseControlRecord = True
'If the first record of your EBCDIC file is filler (junk), then set this to True
trans.IgnoreFirstRecord = True
'EBCDIC files are written in block lengths, set your block length (Example: 134, 900, Etc.)
trans.BlockLength = 900
'This method will actually translate your source file and output it to the specified destination file path
trans.TranslateFile(sSourcePath, sDestinationPath)
'Here is a alternate example:
'No Control record is used
'trans.UseControlRecord = False
'Translate the whole file, including the first record
'trans.IgnoreFirstRecord = False
'Set the block length
'trans.BlockLength = 134
'Translate...
'trans.TranslateFile(sSourcePath, sDestinationPath)
'*** Some additional methods that you can use are:
'Trim off leading characters from left side of string (position 0 to...)
'trans.LTrim = 15
'Translate 1 EBCDIC character to an ASCII character
'Dim strASCIIChar as String = trans.TranslateCharacter("S")
'Translate an EBCDIC character array to an ASCII string
'trans.TranslateCharacters(chrEBCDICArray)
'Translates an EBCDIC string to an ASCII string
'Dim strASCII As String = trans.TranslateString("EBCDIC String")
End Sub
#End Region 'Example
'Translate characters from EBCDIC to ASCII
Private ASCIIEncoding As Encoding = Encoding.ASCII
Private EBCDICEncoding As Encoding = Encoding.GetEncoding(37) 'EBCDIC
'Block Length: Can be fixed (Ex: 134).
Private miBlockLength As Integer = 0
Private mbUseControlRec As Boolean = True 'If set to False, will return exact block length
Private mbIgnoreFirstRecord As Boolean = True 'Will Ignore first record if set to true (First record may be filler)
Private miLTrim As Integer = 0
''' <summary>
''' Translates SourceFile from EBCDIC to ASCII. Writes output to file path specified by DestinationFile parameter.
''' Set the BlockLength Property to designate block size to read.
''' </summary>
''' <param name="SourceFile">Enter the path of the Source File.</param>
''' <param name="DestinationFile">Enter the path of the Destination File.</param>
''' <remarks></remarks>
Public Sub TranslateFile(ByVal SourceFile As String, ByVal DestinationFile As String)
Dim iRecordLength As Integer 'Stores length of a record, not including the length of the Control Record (if used)
Dim sRecord As String = "" 'Stores the actual record
Dim iLineSpace As Integer = 1 'LineSpace: 1 for Single Space, 2 for Double Space, 3 for Triple Space...
Dim iControlPosSix As Byte() 'Stores the 6th character of a Control Record (used to calculate record length)
Dim iControlRec As Byte() 'Stores the EBCDIC Control Record (First 6 characters of record)
Dim bEOR As Boolean 'End of Record Flag
Dim bBOF As Boolean = True 'Beginning of file
Dim iConsumedChars As Integer = 0 'Stores the number of consumed characters in the current block
Dim bIgnoreRecord As Boolean = mbIgnoreFirstRecord 'Ignores the first record if set.
Dim ControlArray(5) As Char 'Stores Control Record (first 6 bytes)
Dim chrArray As Char() 'Stores characters just after read from file
Dim sr As New StreamReader(SourceFile, EBCDICEncoding)
Dim sw As New StreamWriter(DestinationFile)
'Set the RecordLength to the RecordLength Property (below)
iRecordLength = miBlockLength
'Loop through entire file
Do Until sr.EndOfStream = True
'If using a Control Record, then check record for valid data.
If mbUseControlRec = True Then
'Read the Control Record (first 6 characters of the record)
sr.ReadBlock(ControlArray, 0, 6)
'Update the value of consumed (read) characters
iConsumedChars += ControlArray.Length
'Get the bytes of the Control Record Array
iControlRec = EBCDICEncoding.GetBytes(ControlArray)
'Set the line spacing (position 3 divided by 64)
' (64 decimal = Single Spacing; 128 decimal = Double Spacing)
iLineSpace = iControlRec(2) / 64
'Check the Control record for End of File
'If the Control record has a 8 or 10 in position 1, and a 1 in postion 2, then it is the end of the file
If (iControlRec(0) = 8 OrElse iControlRec(0) = 10) AndAlso _
iControlRec(1) = 1 Then
If bBOF = False Then
Exit Do
Else
'The Beginning of file flag is set to true by default, so when the first
' record is encountered, it is bypassed and the bBOF flag is set to False
bBOF = False
End If 'If bBOF = Fals
End If 'If (iControlRec(0) = 8 OrElse
'Set the default value for the End of Record flag to True
' If the Control Record has all zeros, then it's True, else False
bEOR = True
'If the Control record contains all zeros, bEOR will stay True, else it will be set to False
For i As Integer = 0 To 5
If iControlRec(i) > 0 Then
bEOR = False
Exit For
End If 'If iControlRec(i) > 0
Next 'For i As Integer = 0 To 5
If bEOR = False Then
'Convert EBCDIC character to ASCII
'Multiply the 6th byte by 6 to get record length
' Why multiply by 6? Because it works.
iControlPosSix = EBCDICEncoding.GetBytes(ControlArray(5))
'If the 4th position of the control record is an 8, then add 6
' to the record length to pick up remaining characters.
If iControlRec(3) = 8 Then
iRecordLength = CInt(iControlPosSix(0)) * 6 + 6
Else
iRecordLength = CInt(iControlPosSix(0)) * 6
End If
'Add the length of the record to the Consumed Characters counter
iConsumedChars += iRecordLength
Else
'If the Control Record had all zeros in it, then it is the end of the Block.
'Consume the remainder of the block so we can continue at the beginning of the next block.
ReDim chrArray(miBlockLength - iConsumedChars - 1)
'ReDim chrArray(iRecordLength - iConsumedChars - 1)
'Consume (read) the remaining characters in the block.
' We are not doing anything with them because they are not actual records.
'sr.ReadBlock(chrArray, 0, iRecordLength - iConsumedChars)
sr.ReadBlock(chrArray, 0, miBlockLength - iConsumedChars)
'Reset the Consumed Characters counter
iConsumedChars = 0
'Set the Record Length to 0 so it will not be processed below.
iRecordLength = 0
End If ' If bEOR = False
End If 'If mbUseControlRec = True
If iRecordLength > 0 Then
'Resize our array, dumping previous data. Because Arrays are Zero (0) based, subtract 1 from the Record length.
ReDim chrArray(iRecordLength - 1)
'Read the specfied record length, without the Control Record, because we already consumed (read) it.
sr.ReadBlock(chrArray, 0, iRecordLength)
'Copy Character Array to String Array, Converting in the process, then Join the Array to a string
sRecord = Join(Array.ConvertAll(chrArray, New Converter(Of Char, String)(AddressOf ChrToStr)), "")
'If the record length was 0, then the Join method may return Nothing
If IsNothing(sRecord) = False Then
If bIgnoreRecord = True Then
'Do nothing - bypass record
'Reset flag
bIgnoreRecord = False
Else
'Write the line out, LTrimming the specified number of characters.
If sRecord.Length >= miLTrim Then
sw.WriteLine(sRecord.Remove(0, miLTrim))
Else
sw.WriteLine(sRecord.Remove(0, sRecord.Length))
End If ' If sRecord.Length >= miLTrim
'Write out the number of blank lines specified by the 3rd control character.
For i As Integer = 1 To iLineSpace - 1
sw.WriteLine("")
Next 'For i As Integer = 1 To iLineSpace
End If 'If bIgnoreRecord = True
'Obviously, if we have read more characters from the file than the designated size of the block,
' then subtract the number of characters we have read into the next block from the block size.
If iConsumedChars > miBlockLength Then
'If iConsumedChars > iRecordLength Then
iConsumedChars = iConsumedChars - miBlockLength
'iConsumedChars = iConsumedChars - iRecordLength
End If
End If 'If IsNothing(sRecord) = False
End If 'If iRecordLength > 0
'Allow computer to process (works in a class module, not in a dll)
'Application.DoEvents()
Loop
'Destroy StreamReader (sr)
sr.Close()
sr.Dispose()
'Destroy StreamWriter (sw)
sw.Close()
sw.Dispose()
End Sub
''' <summary>
''' Translates 1 EBCDIC Character (Char) to an ASCII String
''' </summary>
''' <param name="chr"></param>
''' <returns></returns>
''' <remarks></remarks>
Private Function ChrToStr(ByVal chr As Char) As String
Dim sReturn As String = ""
'Convert character into byte
Dim EBCDICbyte As Byte() = EBCDICEncoding.GetBytes(chr)
'Convert EBCDIC byte to ASCII byte
Dim ASCIIByte As Byte() = Encoding.Convert(EBCDICEncoding, ASCIIEncoding, EBCDICbyte)
sReturn = Encoding.ASCII.GetString(ASCIIByte)
Return sReturn
End Function
''' <summary>
''' Translates an EBCDIC String to an ASCII String
''' </summary>
''' <param name="sStringToTranslate"></param>
''' <returns>String</returns>
''' <remarks></remarks>
Public Function TranslateString(ByVal sStringToTranslate As String) As String
Dim i As Integer = 0
Dim sReturn As New System.Text.StringBuilder()
'Loop through the string and translate each character
For i = 0 To sStringToTranslate.Length - 1
sReturn.Append(ChrToStr(sStringToTranslate.Substring(i, 1)))
Next
Return sReturn.ToString()
End Function
''' <summary>
''' Translates 1 EBCDIC Character (Char) to an ASCII String
''' </summary>
''' <param name="sCharacterToTranslate"></param>
''' <returns>String</returns>
''' <remarks></remarks>
Public Function TranslateCharacter(ByVal sCharacterToTranslate As Char) As String
Return ChrToStr(sCharacterToTranslate)
End Function
''' <summary>
''' Translates an EBCDIC Character (Char) Array to an ASCII String
''' </summary>
''' <param name="sCharacterArrayToTranslate"></param>
''' <returns>String</returns>
''' <remarks>Remarks</remarks>
Public Function TranslateCharacters(ByVal sCharacterArrayToTranslate As Char()) As String
Dim sReturn As String = ""
'Copy Character Array to String Array, Converting in the process, then Join the Array to a string
sReturn = Join(Array.ConvertAll(sCharacterArrayToTranslate, _
New Converter(Of Char, String)(AddressOf ChrToStr)), "")
Return sReturn
End Function
''' <summary>
''' Block Length must be set. You can set the BlockLength for specific block sizes (Ex: 134).
''' Set UseControlRecord = False for files with specific block sizes (Default is True)
''' </summary>
''' <value>0</value>
''' <returns>Integer</returns>
''' <remarks></remarks>
Public Property BlockLength() As Integer
Get
Return miBlockLength
End Get
Set(ByVal value As Integer)
miBlockLength = value
End Set
End Property
''' <summary>
''' Determines whether a ControlKey is used to calculate RecordLength of valid data
''' </summary>
''' <value>Default value is True</value>
''' <returns>Boolean</returns>
''' <remarks></remarks>
Public Property UseControlRecord() As Boolean
Get
Return mbUseControlRec
End Get
Set(ByVal value As Boolean)
mbUseControlRec = value
End Set
End Property
''' <summary>
''' Ignores first record if set (Default is True)
''' </summary>
''' <value>Default is True</value>
''' <returns>Boolean</returns>
''' <remarks></remarks>
Public Property IgnoreFirstRecord() As Boolean
Get
Return mbIgnoreFirstRecord
End Get
Set(ByVal value As Boolean)
mbIgnoreFirstRecord = value
End Set
End Property
''' <summary>
''' Trims the left side of every string the specfied number of characters. Default is 0.
''' </summary>
''' <value>Default is 0.</value>
''' <returns>Integer</returns>
''' <remarks></remarks>
Public Property LTrim() As Integer
Get
Return miLTrim
End Get
Set(ByVal value As Integer)
miLTrim = value
End Set
End Property
End Class
回答by Paul Morgan
If the original data was in EBCDIC your COMP-3 field has been garbled. The FTP process has done an EBCDIC to ASCII translation of the byte values in the COMP-3 field which isn't what you want. To correct this you can:
如果原始数据在 EBCDIC 中,则您的 COMP-3 字段已出现乱码。FTP 进程已经完成了 COMP-3 字段中字节值的 EBCDIC 到 ASCII 转换,这不是您想要的。要纠正此问题,您可以:
1) Use BINARY mode for the transfer so you get the raw EBCDIC data. Then you convert the COMP-3 field to a number and translate any other EBCDIC text on the record to ASCII. A packed field stores each digit in a half byte with the lower half byte as a sign (F is positive and other values, usually D or E, are negative). Storing 123.4 in a PIC 999.99 USAGE COMP-3 would be X'01234F' (three bytes) and -123 in the same field is X'01230D'.
1) 使用 BINARY 模式进行传输,以便获得原始 EBCDIC 数据。然后将 COMP-3 字段转换为数字,并将记录上的任何其他 EBCDIC 文本转换为 ASCII。压缩字段将每个数字存储在半字节中,低半字节作为符号(F 为正,其他值,通常为 D 或 E,为负)。将 123.4 存储在 PIC 999.99 USAGE COMP-3 中将是 X'01234F'(三个字节),同一字段中的 -123 是 X'01230D'。
2) Have the sender convert the field into a USAGE IS DISPLAY SIGN IS LEADING(or TRAILING) numeric field. This stores the number as a string of EBCDIC numeric digits with the sign as a separate negative(-) or blank character. All digits and the sign translate correctly to their ASCII equivalent on the FTP transfer.
2) 让发件人将该字段转换为 USAGE IS DISPLAY SIGN IS LEADING(或 TRAILING)数字字段。这将数字存储为一串 EBCDIC 数字,符号为单独的负 (-) 或空白字符。所有数字和符号在 FTP 传输中正确转换为它们的 ASCII 等价物。
回答by bubbassauro
Some useful links for EBCDIC translation:
EBCDIC 翻译的一些有用链接:
Translation table - useful to do check some of the values in the packed decimal fields: http://www.simotime.com/asc2ebc1.htm
转换表 - 用于检查压缩十进制字段中的一些值:http: //www.simotime.com/asc2ebc1.htm
List of code pages in msdn:
http://msdn.microsoft.com/en-us/library/dd317756(VS.85).aspx
msdn 中的代码页列表:http: //msdn.microsoft.com/en-us/library/dd317756(VS.85)
.aspx
And a piece of code to convert the byte array fields in C#:
以及一段在C#中转换字节数组字段的代码:
// 500 is the code page for IBM EBCDIC International
System.Text.Encoding enc = new System.Text.Encoding(500);
string value = enc.GetString(byteArrayField);
回答by Steve old guy
The packed fields are the same in EBCDIC or ASCII. Do not run the EBCDIC to ASCII conversion on them. In .Net dump them into a byte[].
EBCDIC 或 ASCII 中的打包字段相同。不要对它们运行 EBCDIC 到 ASCII 的转换。在 .Net 中,将它们转储到 byte[] 中。
You use bitwise masks and shifts to pack/unpack. -- But bitwise ops only apply to integer types in .Net so you need to jump through some hoops!
您使用按位掩码和移位来打包/解包。-- 但按位运算仅适用于 .Net 中的整数类型,因此您需要跳过一些障碍!
A good COBOL or C artist can point you in the right direction.
优秀的 COBOL 或 C 艺术家可以为您指明正确的方向。
Find one of the old guys and pay your dues (about three beers should do it).
找一个老家伙并支付你的会费(大约三杯啤酒就可以了)。
回答by John
I have been watching the posts on numerous boards concerning converting Comp-3 BCD data from "legacy" mainframe files to something useable in C#. First, I would like to say that I am less than enamoured by the responses that some of these posts have received - especially those that have said essentially "why are you bothering us with these non-C#/C++ related posts" and also "If you need an answer about some sort of COBOL convention, why don't you go visit a COBOL oriented site". This, to me, is complete BS as there is going to be a need for probably many years to come, (unfortunately), for software developers to understand how to deal with some of these legacy issues that exist in THE REAL WORLD. So, even if I get slammed on this post for the following code, I am going to share with you a REAL WORLD experience that I had to deal with regarding COMP-3/EBCDIC conversion (and yes, I am he who talks of "floppy disks, paper-tape, Disc Packs etc... - I have been a software engineer since 1979").
我一直在看许多板上关于将 Comp-3 BCD 数据从“遗留”大型机文件转换为可用于 C# 的内容的帖子。首先,我想说的是,我对这些帖子中的一些收到的回复并不着迷——尤其是那些基本上说“你为什么用这些非 C#/C++ 相关的帖子来打扰我们”以及“如果您需要有关某种 COBOL 约定的答案,为什么不去访问一个面向 COBOL 的站点”。对我来说,这完全是废话,因为可能在未来很多年(不幸的是)软件开发人员需要了解如何处理现实世界中存在的一些遗留问题。所以,即使我因为下面的代码在这篇文章上受到猛烈抨击,
First - understand that any file that you read from a legacy main-frame system like IBM is going to present the data to you in EBCDIC format and in order to convert any of that data to a C#/C++ string you can deal with you are going to have to use the proper code page translation to get the data into ASCII format. A good example of how to handle this would be:
首先 - 了解您从像 IBM 这样的传统大型机系统读取的任何文件都将以 EBCDIC 格式向您呈现数据,并且为了将这些数据中的任何一个转换为您可以处理的 C#/C++ 字符串,您是将不得不使用正确的代码页转换来将数据转换为 ASCII 格式。如何处理这个问题的一个很好的例子是:
StreamReader readFile = new StreamReader(path, Encoding.GetEncoding(037); // 037 = EBCDIC to ASCII translation.
StreamReader readFile = new StreamReader(path, Encoding.GetEncoding(037); // 037 = EBCDIC 到 ASCII 的转换。
This will ensure that anything that you read from this stream will then be converted to ASCII and can be used in a string format. This includes "Zoned Decimal" (Pic 9) and "Text" (Pic X) fields as declared by COBOL. However, this does not necessarily convert COMP-3 fields to the correct "binary" equivelant when read into a char[] or byte[] array. To do this, the only way that you are ever going to get this translated properly (even using UTF-8, UTF-16, Default or whatever) code pages, you are going to want to open the file like this:
这将确保您从此流中读取的任何内容都将转换为 ASCII 并可以以字符串格式使用。这包括 COBOL 声明的“分区十进制”(图 9)和“文本”(图 X)字段。但是,当读入 char[] 或 byte[] 数组时,这不一定将 COMP-3 字段转换为正确的“二进制”等价物。要做到这一点,您将得到正确翻译(即使使用 UTF-8、UTF-16、默认或其他)代码页的唯一方法,您将希望像这样打开文件:
FileStream fileStream = new FileStream(path, FIleMode.Open, FIleAccess.Read, FileShare.Read);
FileStream fileStream = new FileStream(path, FIleMode.Open, FIleAccess.Read, FileShare.Read);
Of course, the "FileShare.Read" option is "optional".
当然,“FileShare.Read”选项是“可选的”。
When you have isolated the field that you want to convert to a decimal value (and then subsequently to an ASCII string if need be), you can use the following code - and this has been basically stolen from the MicroSoft "UnpackDecimal" posting that you can get at:
当您已经隔离了要转换为十进制值的字段(如果需要,然后随后转换为 ASCII 字符串),您可以使用以下代码 - 这基本上是从 MicroSoft“UnpackDecimal”发布的您窃取的可以到达:
I have isolated (I think) what are the most important parts of this logic and consolidated it into two a method that you can do with what you want. For my purposes, I chose to leave this as returning a Decimal value which I could then do with what I wanted. Basically, the method is called "unpack" and you pass it a byte[] array (no longer than 12 bytes) and the scale as an int, which is the number of decimal places you want to have returned in the Decimal value. I hope this works for you as well as it did for me.
我已经分离出(我认为)这个逻辑中最重要的部分,并将其合并为两种方法,您可以根据需要进行操作。出于我的目的,我选择将其保留为返回 Decimal 值,然后我可以使用我想要的值。基本上,该方法称为“解包”,您将一个 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);
}
If you have any questions, post them on here - because I suspect that I am going to get "flamed" like everyone else who has chosen to post questions that are pertinent to todays issues...
如果您有任何问题,请在此处发布 - 因为我怀疑我会像其他选择发布与当今问题相关的问题的其他人一样“着火”......
Thanks, John - The Elder.
谢谢,约翰 - 长老。