У меня есть ряд плоских файлов ASCII, поступающих с мэйнфрейма для обработки приложением C#. Был представлен новый канал с полем упакованного десятичного числа (COMP-3), которое необходимо преобразовать в числовое значение.
Файлы передаются через FTP в режиме передачи ASCII. Я обеспокоен тем, что двоичное поле может содержать то, что будет интерпретироваться как очень низкие коды ASCII или управляющие символы вместо значения - или, что еще хуже, может быть потеряно в процессе FTP.
Более того, поля читаются как строки. У меня может быть возможность обойти эту часть (то есть какой-то поток), но бизнес будет сопротивляться.
Требование гласило: «Преобразовать из HEX в ASCII», но явно не давало правильных значений. Любая помощь будет оценена; он не обязательно должен зависеть от языка, если вы можете объяснить логику процесса преобразования.





Приношу свои извинения, если я ошибаюсь, но, возможно, этот пример кода, который я вставлю сюда, может вам помочь. Это пришло из 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
Нет, этого не произошло. Учитывая либо числа в виде открытого текста, либо данные COMP-3, он выводит кучу мусора. То, что мне нужно, будет переводить '<' в '4C', 'b' в '82', '2' в F2 и '0' в 'F0', согласно моим образцам данных. спасибо за попытку.
Прежде всего вы должны устранить проблемы трансляции конца строки (EOL), которые будут вызваны режимом передачи ASCII. Вы совершенно правы, опасаясь повреждения данных, когда значения BCD совпадают с символами EOL. Худший аспект этой проблемы - это то, что она будет возникать редко и неожиданно.
Лучшее решение - сменить режим передачи на BIN. Это уместно, поскольку данные, которые вы передаете, являются двоичными. Если невозможно использовать правильный режим передачи FTP, вы можете отменить повреждение режима ASCII с помощью кода. Все, что вам нужно сделать, это преобразовать пары \ r \ n обратно в \ n. На вашем месте я бы удостоверился, что это хорошо проверено.
Как только вы разобрались с проблемой EOL, преобразование COMP-3 довольно простое. Мне удалось найти эта статья в базе знаний MS с образцом кода на BASIC. См. Ниже порт VB.NET этого кода.
Поскольку вы имеете дело со значениями COMP-3, формат файла, который вы читаете, почти наверняка имеет фиксированный размер записи с фиксированной длиной поля. На вашем месте я бы достал спецификацию формата файла, прежде чем вы пойдете дальше. Вы должны использовать BinaryReader для работы с этими данными. Если кто-то возражает против этого, я уйду. Пусть найдут кого-нибудь, кто потакает своей глупости.
Вот порт VB.NET образца кода BASIC. Я не тестировал это, потому что у меня нет доступа к файлу 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
Будет ли режим двоичной передачи вызывать какие-либо проблемы с остальной частью файла, содержащего данные ASCII? Восходящий процесс дисквалифицирует любые строки, которые кажутся короче, чем заранее описанная длина, поэтому я могу отодвинуть его и изменить его на номер открытого текста. Спасибо.
На самом деле, этот код был в QBasic ... который я знаю, но не использовал около 10 лет :) Я настолько ржавый, что не смог преобразовать его в C#, не говоря уже о том, как использовать это для 8 -символьное двоичное поле в файле ascii ... Но спасибо за попытку.
Двоичная передача по FTP вообще не изменит файл.
Если это принятый ответ, не могли бы вы рассказать нам, как вы в итоге заставили это работать ?! Я искал решение той же проблемы и оказался здесь. Пожалуйста помоги!!! Я не понимаю QBasic Я слишком молод.
Я добавил порт VB.NET. Я не уверен, насколько это полезно, но это было интересное упражнение по переносу старого кода QBASIC на VB.NET 3.5. Удивительно, как мало изменений потребовалось для его компиляции и запуска. Однако я бы с осторожностью относился к операторам ввода, я не могу гарантировать, что они будут правильно читать двоичные файлы. Если у вас есть соответствующий тестовый файл, вы сможете проверить правильность (или некорректность) кода.
Если исходные данные были в формате EBCDIC, ваше поле COMP-3 было искажено. Процесс FTP выполнил преобразование EBCDIC в ASCII байтовых значений в поле COMP-3, что вам не нужно. Чтобы исправить это, вы можете:
1) Используйте режим BINARY для передачи, чтобы получить необработанные данные EBCDIC. Затем вы конвертируете поле COMP-3 в число и переводите любой другой текст EBCDIC в записи в ASCII. В упакованном поле каждая цифра полубайта хранится с младшим полубайтом в качестве знака (F положительное значение, а другие значения, обычно D или E, отрицательные). Сохранение 123,4 в PIC 999.99 USAGE COMP-3 будет X'01234F '(три байта), а -123 в том же поле будет X'01230D'.
2) Попросите отправителя преобразовать это поле в числовое поле USAGE IS DISPLAY SIGN IS LEADING (или TRAILING). Это сохраняет число в виде строки цифровых цифр EBCDIC со знаком в виде отдельного отрицательного (-) или пустого символа. Все цифры и знак правильно переводятся в их эквивалент ASCII при передаче по FTP.
Спасибо, мы уже позаботились об этом. Зависимость от восходящего потока изменила плоский файл, чтобы предоставить номер в удобочитаемом формате.
А как насчет подразумеваемой десятичной дроби? Как в: PIC 999v99 COMP-3
Подразумевается, что десятичной запятой нет в сохраненном значении. С PIC 999.99 USAGE IS DISPLAY SIGN LEADING вы получаете строку -12345 для значения -123,45 или b12345 (начальный пробел) для положительного значения 123,45. Числовой текст действительно преобразуется правильно при выполнении FTP из EBCDIC в ASCII.
Некоторые полезные ссылки для перевода EBCDIC:
Таблица перевода - полезно для проверки некоторых значений в упакованных десятичных полях: http://www.simotime.com/asc2ebc1.htm
Список кодовых страниц в msdn:
http://msdn.microsoft.com/en-us/library/dd317756(VS.85).aspx
И фрагмент кода для преобразования полей байтового массива в C#:
// 500 is the code page for IBM EBCDIC International
System.Text.Encoding enc = new System.Text.Encoding(500);
string value = enc.GetString(byteArrayField);
Упакованные поля одинаковы в EBCDIC или ASCII. Не запускайте на них преобразование EBCDIC в ASCII. В .Net выгружать их в байт [].
Вы используете поразрядные маски и сдвиги для упаковки / распаковки. - Но побитовые операции применимы только к целочисленным типам в .Net, поэтому вам нужно перепрыгнуть через несколько обручей!
Хороший художник на COBOL или C может указать вам правильное направление.
Найдите одного из стариков и заплатите по взносам (на это должно хватить примерно трех бутылок пива).
Я просматривал сообщения на многочисленных досках, касающиеся преобразования данных Comp-3 BCD из "устаревших" файлов мэйнфрейма в что-то, что можно использовать на C#. Во-первых, я хотел бы сказать, что меня менее чем восхищают ответы, полученные на некоторые из этих сообщений, особенно те, в которых, по сути, говорилось: «Почему вы беспокоите нас этими сообщениями, не связанными с C# / C++», а также «Если вам нужен ответ о какой-то конвенции COBOL, почему бы вам не посетить сайт, ориентированный на COBOL ». Для меня это полная ерунда, поскольку, вероятно, в течение многих лет (к сожалению) разработчикам программного обеспечения потребуется понять, как справляться с некоторыми из этих устаревших проблем, существующих в РЕАЛЬНОМ МИРЕ. Итак, даже если я получу этот пост из-за следующего кода, я собираюсь поделиться с вами РЕАЛЬНЫМ МИРОВЫМ опытом, с которым мне пришлось иметь дело в отношении преобразования COMP-3 / EBCDIC (и да, я тот, кто говорит о " дискеты, бумажная лента, пакеты дисков и т. д. - Я инженер-программист с 1979 года »).
Во-первых, поймите, что любой файл, который вы читаете из устаревшей системы мэйнфрейма, такой как IBM, будет представлять вам данные в формате EBCDIC, и чтобы преобразовать любые из этих данных в строку C# / C++, с которой вы можете иметь дело, придется использовать правильный перевод кодовой страницы, чтобы получить данные в формате ASCII. Хороший пример того, как с этим справиться:
StreamReader readFile = new StreamReader (path, Encoding.GetEncoding (037); // 037 = преобразование EBCDIC в ASCII.
Это гарантирует, что все, что вы читаете из этого потока, будет преобразовано в ASCII и может быть использовано в строковом формате. Сюда входят поля «Зонированная десятичная дробь» (рис. 9) и «Текст» (рис. X), как объявлено в COBOL. Однако это не обязательно преобразует поля COMP-3 в правильный «двоичный» эквивалент при чтении в массив char [] или byte []. Чтобы сделать это, единственный способ, которым вы когда-либо собираетесь правильно перевести это (даже используя кодовые страницы UTF-8, UTF-16, Default или что-то еще), вы захотите открыть файл следующим образом:
FileStream fileStream = новый FileStream (путь, FIleMode.Open, FIleAccess.Read, FileShare.Read);
Конечно, опция «FileShare.Read» является «необязательной».
Когда вы изолировали поле, которое хотите преобразовать в десятичное значение (а затем, при необходимости, в строку ASCII), вы можете использовать следующий код - и он в основном был украден из публикации MicroSoft «UnpackDecimal», которую вы можно попасть по адресу:
Я выделил (как мне кажется) самые важные части этой логики и объединил их в два метода, с помощью которых вы можете делать то, что хотите. Для моих целей я решил оставить это как возвращающее значение Decimal, которое я затем мог бы делать с тем, что хотел. По сути, метод называется «распаковать», и вы передаете ему массив byte [] (не длиннее 12 байт) и масштаб как int, то есть количество десятичных знаков, которое вы хотите вернуть в десятичном значении. Надеюсь, это сработает для вас так же, как и для меня.
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);
}
Если у вас есть какие-либо вопросы, опубликуйте их здесь - потому что я подозреваю, что меня «разозлят», как и всех остальных, кто решил публиковать вопросы, относящиеся к сегодняшним проблемам ...
Спасибо, Джон - Старший.
как прочитать файл и передать его в Unpack, я использую файловый поток, чтобы открыть файл, а затем использую StreamReader для чтения каждой строки, а затем конвертирую строку в байт для передачи методу Unpack до конца потока. byte [] b = ASCIIEncoding.ASCII.GetBytes (reader.ReadLine ()); Распаковать (b, 0); Но он дает неправильные значения во время преобразования номера двоичного формата, который составляет 13 цифр, не могли бы вы предложить, как прочитать файл, преобразовать его в байт и перейти к распаковке, если я делаю что-то не так. Спасибо
«Тип передачи ASCII» передает файлы как обычные текстовые файлы. Таким образом, файлы становятся поврежденными, когда мы передаем файлы упакованных десятичных или двоичных данных с типом передачи ASCII. «Тип двоичной передачи» передает данные в двоичном режиме, при котором файлы обрабатываются как двоичные данные, а не текстовые данные. Таким образом, мы должны использовать здесь двоичный тип передачи. Ссылка: https://www.codeproject.com/Tips/673240/EBCDIC-to-ASCII-Converter
Когда ваш файл будет готов, вот код для преобразования упакованного десятичного числа в понятное для человека десятичное.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
var path = @"C:\FileName.BIN.dat";
var templates = new List<Template>
{
new Template{StartPos=1,CharLength=4,Type = "AlphaNum"},
new Template{StartPos=5,CharLength=1,Type = "AlphaNum"},
new Template{StartPos=6,CharLength=8,Type = "AlphaNum"},
new Template{StartPos=14,CharLength=1,Type = "AlphaNum"},
new Template{StartPos=46,CharLength=4,Type = "Packed",DecimalPlace=2},
new Template{StartPos=54,CharLength=5,Type = "Packed",DecimalPlace=0},
new Template{StartPos=60,CharLength=4,Type = "Packed",DecimalPlace=2},
new Template{StartPos=64,CharLength=1,Type = "AlphaNum"}
};
var allBytes = File.ReadAllBytes(path);
for (int i = 0; i < allBytes.Length; i += 66)
{
var IsLastline = (allBytes.Length - i) < 66;
var lineLength = IsLastline ? 64 : 66;
byte[] lineBytes = new byte[lineLength];
Array.Copy(allBytes, i, lineBytes, 0, lineLength);
var outArray = new string[templates.Count];
int index = 0;
foreach (var temp in templates)
{
byte[] amoutBytes = new byte[temp.CharLength];
Array.Copy(lineBytes, temp.StartPos - 1, amoutBytes, 0,
temp.CharLength);
var final = "";
if (temp.Type == "Packed")
{
final = Unpack(amoutBytes, temp.DecimalPlace).ToString();
}
else
{
final = ConvertEbcdicString(amoutBytes);
}
outArray[index] = final;
index++;
}
Console.WriteLine(string.Join(" ", outArray));
}
Console.ReadLine();
}
private static string ConvertEbcdicString(byte[] ebcdicBytes)
{
if (ebcdicBytes.All(p => p == 0x00 || p == 0xFF))
{
//Every byte is either 0x00 or 0xFF (fillers)
return string.Empty;
}
Encoding ebcdicEnc = Encoding.GetEncoding("IBM037");
string result = ebcdicEnc.GetString(ebcdicBytes); // convert EBCDIC Bytes ->
Unicode string
return result;
}
private static 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, AlphaNumd "F" hex is unsigned.
var ff = nibble(inp, 0);
switch (ff)
{
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 static int nibble(byte[] inp, int nibbleNo)
{
int b = inp[inp.Length - 1 - nibbleNo / 2];
return (nibbleNo % 2 == 0) ? (b & 0x0000000F) : (b >> 4);
}
class Template
{
public string Name { get; set; }
public string Type { get; set; }
public int StartPos { get; set; }
public int CharLength { get; set; }
public int DecimalPlace { get; set; }
}
}
}
Файлы должны передаваться как двоичные. Вот более короткий способ сделать это:
using System.Linq;
namespace SomeNamespace
{
public static class SomeExtensionClass
{
/// <summary>
/// computes the actual decimal value from an IBM "Packed Decimal" 9(x)v9 (COBOL) format
/// </summary>
/// <param name = "value">byte[]</param>
/// <param name = "precision">byte; decimal places, default 2</param>
/// <returns>decimal</returns>
public static decimal FromPackedDecimal(this byte[] value, byte precision = 2)
{
if (value.Length < 1)
{
throw new System.InvalidOperationException("Cannot unpack empty bytes.");
}
double power = System.Math.Pow(10, precision);
if (power > long.MaxValue)
{
throw new System.InvalidOperationException(
$"Precision too large for valid calculation: {precision}");
}
string hex = System.BitConverter.ToString(value).Replace("-", "");
var bytes = Enumerable.Range(0, hex.Length)
.Select(x => System.Convert.ToByte($"0{hex.Substring(x, 1)}", 16))
.ToList();
long place = 1;
decimal ret = 0;
for (int i = bytes.Count - 2; i > -1; i--)
{
ret += (bytes[i] * place);
place *= 10;
}
ret /= (long)power;
return (bytes.Last() & (1 << 7)) != 0 ? ret * -1 : ret;
}
}
}
Вау, спасибо. Я не буду обвинять вас в большой пасте (это все-таки сайт ответов) ... Проголосуйте за ответ быстрый, который выглядит правильным - Но я не могу принять его, пока не вернусь на работу в понедельник. К счастью, я тоже знаю VB.NET ... Спасибо.