在 ASCII 转换中解包 EBCDIC 压缩十进制数(COMP-3)

7
我正在使用Jon Skeet的.NET EBCDIC实现来读取通过FTP从主机系统以二进制模式下载的VSAM文件。它在这种编码下的读写非常好用,但是它没有任何可以读取压缩十进制值的功能。我的文件包含这些值,我需要解压它们(显然会增加更多字节的成本)。
如何做到这一点?
我的字段定义为PIC S9(7)V99 COMP-3。

紧缩十进制是否是EBCDIC格式的一部分,还是只是偶然使用了这两种格式的文件?恐怕我不熟悉那种格式。 :) - Jon Skeet
这是一个同时使用两种方式的文件。这是一种老派的方法,通过压缩数字来节省字节。一个-99999.99可以存储在4个字节中。 - Josh Stodola
http://www.3480-3590-data-conversion.com/article-packed-fields.html - Josh Stodola
哈哈,谢谢!为了节省几个字节而付出如此努力确实有点好笑,但是当你有数百万条记录时,我猜这些努力是值得的。 - Josh Stodola
2个回答

4

啊,BCD码。如果你在6502汇编中使用过它,请按喇叭。

当然,最好的方法是让COBOL MOVE为你完成任务!以下是一些可能有用的方法。

(可能性#1) 假设你可以访问主机和源代码,并且输出文件仅供你使用,请修改程序,使其将值移动到一个普通的未压缩PIC S9(7)V99中。

(可能性#2) 假设情况没有那么简单(例如,文件是其他程序的输入,或者无法更改代码),您可以在系统上编写另一个COBOL程序,读取该文件并编写另一个文件。将带有BCD的文件记录布局剪切并粘贴到新程序的输入和输出文件中。将输出版本修改为非压缩格式。读取记录,执行“move corresponding”以传输数据,并写入,直到eof。然后传输那个文件。

(可能性#3) 如果您不能触碰主机,请注意您在评论中链接的文章中的描述。BCD相对简单。它可能像这样简单(vb.net):

Private Function FromBCD(ByVal BCD As String, ByVal intsz As Integer, ByVal decsz As Integer) As Decimal
    Dim PicLen As Integer = intsz + decsz
    Dim result As Decimal = 0
    Dim val As Integer = Asc(Mid(BCD, 1, 1))
    Do While PicLen > 0
        result *= 10D
        result += val \ 16
        PicLen -= 1
        If PicLen > 0 Then
            result *= 10D
            result += val Mod 16
            PicLen -= 1
            BCD = Mid(BCD, 2)
        End If
        val = Asc(Mid(BCD, 1, 1))
    Loop
    If val Mod 16 = &HD& Then
        result = -result
    End If
    Return result / CDec(10 ^ decsz)
End Function

我用了几种不同的调用方式进行测试:

MsgBox(FromBCD("@" & Chr(13 + 16), 2, 1))

例如,是-40.1。但只有少数情况。因此可能仍然不正确。
因此,如果您的comp-3从输入记录布局的第10个字节开始,这将解决它:
dim valu as Decimal = FromBCD(Mid(InputLine,10,5), 7,2))

注意数据转换文章中发送字节数量和V前后的9的公式。

将结果存储在十进制中以避免舍入误差。特别是如果是$$$。浮点数和双精度会让你感到烦恼!即使您不处理它,字符串也更好。

当然,这可能会更难。在我的工作中,大型机每个字节有9位。很严重。这就是为什么前两种可能性如此重要的原因。当然,真正让它们更好的是,您可能只是一名PC程序员,这是让主机程序员为您完成工作的绝佳借口!如果您有这个幸运的选择...

祝好, -Al


1
谢谢Al!也许可以修改COBOL程序或编写另一个程序来解包,但目标是在.NET中完成此操作,以便我们可以直接运行VSAM-> FTP任务。基于二进制表示,我如何知道V(十进制)在哪里?那可能吗?主机是如何知道的?再次感谢您的帮助,我很感激。 - Josh Stodola
Josh,仅凭数据你无法知道V来自哪里;你需要PIC。主机从PIC中跟踪它。这是打孔卡时代典型的精益、节省位的解决方案。但是如果你想一想,系统永远不会没有所需的信息。该死,如果你自己看不到记录布局,你将不得不全部破解! - FastAl
是的,你说得对,我没想到会有办法。我已经确定了所有的偏移量,并且记录布局在这里,我只是好奇是否有一种方法可以通过编程来确定十进制位置。好的,我明天会试用这个函数并告诉你它的效果如何!再次感谢你的帮助,在这里我真希望我能点赞两次。 - Josh Stodola
1
再次感谢您的回答!六年过去了,仍然非常有用。 - Josh Stodola

0

我使用这个扩展方法进行压缩十进制(BCD)转换:

    /// <summary>
    /// computes the actual decimal value from an IBM "Packed Decimal" 9(x)v99 (COBOL COMP-3) 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;
    }

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