Search code examples
c#.netibm-midrangeflat-filecomp-3

C#: Convert COMP-3 Packed Decimal to Human-Readable Value


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.

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.

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.


Solution

  • 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.

    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.

    Once you've dealt with the EOL problem, the COMP-3 conversion is pretty straigtforward. I was able to find this article in the MS knowledgebase with sample code in BASIC. See below for a VB.NET port of this code.

    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.

    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.

    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