EBCDIC转ASCII转换,处理数字值

3

我试图将文件从ECDIC格式转换为ASCII格式,但遇到了一个有趣的问题。这些文件包含一些字段为带符号二进制整数(在记录布局中描述为B4)和长精度数字值(在记录布局中描述为L8)的固定长度记录。我已经成功地转换了字符数据,但不确定如何转换这些数字值。从原系统(IBM 5110)的参考手册中可以找到以下字段描述。

B表示数字数据项的长度(2、4或8个字节),这些数据项采用定点有符号二进制整数格式,需要转换为BASIC内部数据格式。对于记录I/O文件输入,记录中的下一个2、4或8个字节包含要由系统转换为内部数据格式并分配给使用FORM语句指定的变量的有符号二进制值,这些变量在READ FILE或REREAD FILE语句中指定。

L表示数字值的长精度(8个字符)。对于输入,此条目表示记录中的八位长精度值将分配给在READ FILE或REREAD FILE语句中指定的相应数字变量,而无需进行转换。

编辑:这是我用于转换的代码

private void ConvertFile(EbcdicFile file)
{
    if (file == null) return;

    var filePath = Path.Combine(file.Path, file.FileName);
    if (!File.Exists(filePath))
    {
        this.Logger.Info(string.Format("Cannot convert file {0}. It does not exist.", filePath));
        return;
    }

    var ebcdic = Encoding.GetEncoding(37);
    string convertedFilepath = Path.Combine(file.Path, file.ConvertedFileName);
    byte[] fileData = File.ReadAllBytes(filePath);

    if (!file.HasNumericFields)
        File.WriteAllBytes(convertedFilepath, Encoding.Convert(ebcdic, Encoding.ASCII, fileData));
    else
    {
        var convertedFileData = new List<byte>();
        for (int position = 0; position < fileData.Length; position += file.RecordLength)
        {
            var segment = new ArraySegment<byte>(fileData, position, file.RecordLength);
            file.Fields.ForEach(field =>
                {
                    var fieldSegment = segment.Array.Skip(segment.Offset + field.Start - 1).Take(field.Length);
                    if (field.Type.Equals("string", StringComparison.OrdinalIgnoreCase))
                    {
                        convertedFileData.AddRange(
                            Encoding.Convert(ebcdic, Encoding.ASCII, fieldSegment.ToArray())
                            );
                    }
                    else if (field.Type.Equals("B4", StringComparison.OrdinalIgnoreCase))
                    {
                        // Not sure how to convert this field
                    }
                    else if (field.Type.Equals("L8", StringComparison.OrdinalIgnoreCase))
                    {
                        // Not sure how to convert this field
                    }
                });
        }

        File.WriteAllBytes(convertedFilepath, convertedFileData.ToArray());
    }
}

您已经描述了您当前的情况,但并没有提出问题 :-). 我想您想要问的是“如何将IBM 5110 BASIC编写的数据文件中的B4和L8字段类型转换为其他类型”.... 您需要向我们展示一些示例数据(十六进制转储),最好也包括正确的解释。 - Ben
2个回答

2

首先,您需要知道固定的记录大小。使用FileStream.Read()读取一个记录大小的字节。然后使用Encoding.GetString()将其转换为字符串。

然后使用String.SubString()从记录中提取字段。B4只是一个长度为4的SubString调用,L8则是一个长度为8的调用。然后使用Decimal.Parse()将这样的字段进一步转换为数字。您可能需要除以结果,因为不清楚使用了什么固定点乘数。100的胜率很高。


感谢您的反馈。我相信您所描述的是我已经实现的方式。虽然我正在将每个记录处理为字节数组。我会尝试使用带有固定点乘数的Decimal.Parse。这是一个好建议。不幸的是,我只有记录布局和输出的文档,所以这有点像试错。 - Garett

1

好的,我已经弄清楚了如何转换这两个字段。B4字段非常简单直接。它们本质上是一个4字节的数组,可以转换为整数。

//The IBM 5110 were big endian machines, so reverse the array 
if (BitConverter.IsLittleEndian)
    Array.Reverse(by);

int value = BitConverter.ToInt32(by, 0);

L8字段是8字节的数组,代表IBM双精度浮点数。有许多方式可以将其转换为IEEE 754浮点数。可以在以下链接中找到一些示例:

以下是我根据这些文章中的指导使用的版本。

private double IbmFloatToDouble(byte[] value)
{
    if (ReferenceEquals(null, value))
        throw new ArgumentNullException("value");

    if (BitConverter.ToInt64(value, 0) == 0)
        return 0;

    int exponentBias = 64;
    int ibmBase = 16;
    double sign = 0.0D;

    int signValue = (value[0] & 0x80) >> 7;
    int exponentValue = (value[0] & 0x7f);
    double fraction1 = (value[1] << 16) + (value[2] << 8) + value[3];
    double fraction2 = (value[4] << 24) + (value[5] << 16) + (value[6] << 8) + value[7];
    double exponent24 = 16777216.0;             // 2^24
    double exponent56 = 72057594037927936.0;    // 2^56

    double mantissa1 = fraction1 / exponent24;
    double mantissa2 = fraction2 / exponent56;
    double mantissa = mantissa1 + mantissa2;
    double exponent = Math.Pow(ibmBase, exponentValue - exponentBias);

    if (signValue == 0) 
        sign = 1.0;
    else 
        sign = -1.0;

    return (sign * mantissa * exponent);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接