Saltar al contenido

C #: Convertir decimal empaquetado COMP-3 en valor legible por humanos

Este grupo de redactores ha estado largas horas investigando para dar espuestas a tus interrogantes, te regalamos la soluciones por eso deseamos resultarte de mucha apoyo.

Solución:

He estado viendo las publicaciones en numerosos foros sobre la conversión de datos BCD Comp-3 de archivos de mainframe “heredados” a algo utilizable en C #. Primero, me gustaría decir que estoy menos que enamorado de las respuestas que han recibido algunas de estas publicaciones, especialmente aquellas que dicen esencialmente “¿por qué nos molestan con estas publicaciones no relacionadas con C # / C ++” y también “Si necesita una respuesta sobre algún tipo de convención COBOL, ¿por qué no visita un sitio orientado a COBOL? “. Esto, para mí, es una completa tontería, ya que probablemente durante muchos años por venir, (desafortunadamente), los desarrolladores de software comprendan cómo lidiar con algunos de estos problemas heredados que existen en EL MUNDO REAL. Entonces, incluso si me critican en esta publicación por el siguiente código, voy a compartir con ustedes una experiencia del MUNDO REAL con la que tuve que lidiar con respecto a la conversión de COMP-3 / EBCDIC (y sí, soy yo quien habla de ” disquetes, cintas de papel, paquetes de discos, etc … – Soy ingeniero de software desde 1979 “).

Primero, comprenda que cualquier archivo que lea de un sistema de marco principal heredado como IBM le presentará los datos en formato EBCDIC y para convertir cualquiera de esos datos a C # / C ++ string puede tratar con que tendrá que utilizar la traducción de la página de códigos adecuada para obtener los datos en formato ASCII. Un buen ejemplo de cómo manejar esto sería:

StreamReader readFile = new StreamReader (ruta, Encoding.GetEncoding (037); // 037 = EBCDIC a traducción ASCII.

Esto asegurará que todo lo que lea de esta secuencia se convertirá a ASCII y se podrá utilizar en una string formato. Esto incluye los campos “Decimal por zona” (Imagen 9) y “Texto” (Imagen X) según lo declarado por COBOL. Sin embargo, esto no convierte necesariamente los campos COMP-3 en el equivalente “binario” correcto cuando se leen en un carácter.[] o byte[] array. Para hacer esto, la única forma en que lo va a traducir correctamente (incluso usando las páginas de códigos UTF-8, UTF-16, Predeterminado o lo que sea), querrá abrir el archivo de esta manera:

FileStream fileStream = nuevo FileStream (ruta, FIleMode.Open, FIleAccess.Read, FileShare.Read);

Por supuesto, la opción “FileShare.Read” es “opcional”.

Cuando haya aislado el campo que desea convertir a un valor decimal (y luego a un ASCII string si es necesario), puede usar el siguiente código, y esto básicamente ha sido robado de la publicación “UnpackDecimal” de MicroSoft que puede obtener en:

http://www.microsoft.com/downloads/details.aspx?familyid=0e4bba52-cc52-4d89-8590-cda297ff7fbd&displaylang=en

He aislado (creo) cuáles son las partes más importantes de esta lógica y las he consolidado en dos, un método que puedes hacer con lo que quieras. Para mis propósitos, elegí dejar esto como una devolución de un valor decimal que luego podría hacer con lo que quería. Básicamente, el método se llama “desempaquetar” y le pasa un byte[] array (no más de 12 bytes) y la escala como un int, que es el número de lugares decimales que desea que se devuelvan en el valor decimal. Espero que esto te funcione tan bien como a mí.

    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);
    

Si tiene alguna pregunta, publíquela aquí, porque sospecho que me voy a “enfurecer” como todos los demás que han optado por publicar preguntas que son pertinentes a los problemas de hoy …

Gracias, John, el anciano.

En primer lugar, debe eliminar los problemas de traducción de final de línea (EOL) que serán causados ​​por el modo de transferencia ASCII. Tiene toda la razón al preocuparse por la corrupción de datos cuando los valores BCD corresponden a caracteres EOL. El peor aspecto de este problema es que ocurrirá en raras ocasiones e inesperadamente.

La mejor solución es cambiar el modo de transferencia a BIN. Esto es apropiado ya que los datos que está transfiriendo son binarios. Si no es posible utilizar el modo de transferencia FTP correcto, puede deshacer el daño del modo ASCII en el código. Todo lo que tienes que hacer es convertir r n pares de nuevo a n. Si yo fuera usted, me aseguraría de que esto esté bien probado.

Una vez que haya resuelto el problema de EOL, la conversión a COMP-3 es bastante sencilla. Pude encontrar este artículo en la base de conocimientos de MS con código de muestra en BASIC. Consulte a continuación un puerto VB.NET de este código.

Dado que está tratando con valores COMP-3, el formato de archivo que está leyendo casi seguramente tiene tamaños de registro fijos con longitudes de campo fijas. Si yo fuera usted, tendría en mis manos una especificación de formato de archivo antes de continuar con esto. Debería utilizar un BinaryReader para trabajar con estos datos. Si alguien rechaza este punto, me alejaría. Que busquen a alguien más para complacer su locura.

Aquí hay un puerto VB.NET del código de muestra BASIC. No lo he probado porque no tengo acceso a un archivo COMP-3. Si esto no funciona, me referiría al código de muestra original de MS como guía o a las referencias en las otras respuestas a esta pregunta.

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

Si los datos originales estaban en EBCDIC, su campo COMP-3 se ha distorsionado. El proceso FTP ha realizado una traducción de EBCDIC a ASCII de los valores de bytes en el campo COMP-3, que no es lo que desea. Para corregir esto, puede:

1) Utilice el modo BINARIO para la transferencia para obtener los datos EBCDIC sin procesar. Luego, convierte el campo COMP-3 en un número y traduce cualquier otro texto EBCDIC del registro a ASCII. Un campo empaquetado almacena cada dígito en medio byte con el medio byte inferior como un signo (F es positivo y otros valores, generalmente D o E, son negativos). Almacenar 123.4 en un PIC 999.99 USAGE COMP-3 sería X'01234F '(tres bytes) y -123 en el mismo campo es X'01230D'.

2) Haga que el remitente convierta el campo en un campo numérico EL USO ES LA SEÑAL DE PANTALLA ES PRINCIPAL (o RASTREO). Esto almacena el número como un string de dígitos numéricos EBCDIC con el signo como un carácter negativo (-) o en blanco por separado. Todos los dígitos y el signo se traducen correctamente a su equivalente ASCII en la transferencia FTP.

Reseñas y calificaciones

Nos encantaría que puedieras mostrar este tutorial si lograste el éxito.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *