ベンダから提供されたファイルから、数値データと日付データの両方を含む COMP-3 フィールドをアンパックする際に問題が発生しています。
ファイル仕様は、次の情報を提供します。
0066-0070 DATE-OPENED S9(9) COMP-3
仕様は、日付が MMDDYYYY 形式に展開されることを示しています。
このデータ ブロックをファイルから取得すると、それをメモリに読み込むことができ、5 バイトのデータを取得できることがわかります。(ファイルには、1 文字につき 1 バイトが含まれます。)取得されるバイトは次のとおりです。
0: 10
1: 0
2: 18
3: 0
4: 2
最下位桁 (常に表示される場所) に過度に押し込まれた符号はないため、ここでは問題になりません。ビットは次のニブルに展開されます。
0 1 0 0 1 2 0 0 0 2
ここにはいくつかの問題があります:
01001200 が MMDDYYYY 形式の有効な日付を表す可能性はほとんどありませんが、データがフィールドにパックされた方法のようです。
COMP-3 フィールドがアンパックされると、テンプレートは 9 文字に拡張する必要があることを指定しますが、COMP-3 が拡張されると、そのサイズは常に2 倍になります (文字数が偶数の文字列が生成されます)。その結果、予想されるサイズと展開されたサイズの間に不一致が生じます。
Web 上で見つけられるアルゴリズムは、このデータのアンパックには機能しないようです。ソース ファイル内の (おそらく) BCD 値のいずれについても、認識可能な日付を特定できるものはないようです。
現時点では、真の BCD フォーマットを扱っていないのではないかと思います。ただし、ツールではなく常に自分自身を疑うべきであることを念頭に置いて、COMP-3 形式の理解と見ているデータの性質の両方で何が間違っている可能性があるかについての提案を求めています。
形式についての私の理解は、次のソースから取得されます。
- http://www.3480-3590-data-conversion.com/article-reading-cobol-layouts-4.html
- http://www.3480-3590-data-conversion.com/article-packed-fields.html
- http://www.3480-3590-data-conversion.com/article-bcd-binary.html
- http://www.3480-3590-data-conversion.com/article-signed-fields.html
データを解凍する前に、データを EBCDIC から ASCII に、またはその逆に変換しようとしたことは注目に値します。どちらもわかりやすい結果は得られませんでした。インターネットで見つけられるすべてのアルゴリズムを試しましたが、どれも有用な結果を生成していないようです。
結局のところ、私の質問は次のとおりだと思います。ここで実際にBCDまたはCOMP-3データを扱っているのでしょうか?
アップデート
いくつかの質問への回答:
- 値に符号ニブルが含まれていると判断したら、そのニブルをクリアします。
- 私がやっていることを正確に見ることができるように、できる限りすべてのコードを含めました。このクラスは、多くの診断情報 (Bytes プロパティや Nibbles プロパティなど) を提供するように設計されているため、値を解析した後に結果を確認できます。
参考資料として使用するために、元のファイルと解析されていないファイルの両方を手元に用意しています。私が戻ってくると期待している日付は、06152008 のようなものです (これは私の頭から離れていますが、要点はわかります)。私が計算している値は、そのようなものではありません。
リクエストごとに、個々のニブル:
0 1 0 0 1 2 0 0 0 2
そして、私がどのようにそれを行っているかに興味がある人のために、解凍しているクラス:
using System.Collections.Generic;
using System.Linq;
using System.Text;
internal class PackedDecimal
{
#region Fields
private bool _isPositive;
private bool _isNegative;
private bool _isUnsigned = true;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="PackedDecimal"/> class.
/// </summary>
public PackedDecimal()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="PackedDecimal"/> class.
/// </summary>
/// <param name="compressedDecimal">The compressed decimal.</param>
public PackedDecimal(string compressedDecimal)
{
this.ParsedValue = this.Parse(compressedDecimal);
}
#endregion
#region Properties
/// <summary>
/// Gets the bytes.
/// </summary>
public IEnumerable<byte> Bytes { get; private set; }
/// <summary>
/// Gets the hexadecimal values.
/// </summary>
public IEnumerable<string> HexValues { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is positive.
/// </summary>
/// <value>
/// <c>true</c> if this instance is positive; otherwise, <c>false</c>.
/// </value>
public bool IsPositive
{
get { return this._isPositive; }
set
{
this._isNegative = !this.IsPositive;
this._isUnsigned = false;
}
}
/// <summary>
/// Gets or sets a value indicating whether this instance is negative.
/// </summary>
/// <value>
/// <c>true</c> if this instance is negative; otherwise, <c>false</c>.
/// </value>
public bool IsNegative
{
get { return this._isNegative; }
set
{
this._isNegative = value;
this._isPositive = !value;
this._isUnsigned = false;
}
}
/// <summary>
/// Gets a value indicating whether this instance is unsigned.
/// </summary>
/// <value>
/// <c>true</c> if this instance is unsigned; otherwise, <c>false</c>.
/// </value>
public bool IsUnsigned { get { return this._isUnsigned; } }
/// <summary>
/// Gets the nibbles.
/// </summary>
public IEnumerable<int> Nibbles { get; private set; }
/// <summary>
/// Gets the parsed value.
/// </summary>
public string ParsedValue { get; private set; }
#endregion
/// <summary>
/// Parses the specified value.
/// </summary>
/// <param name="value">The value.</param>
/// <returns></returns>
public string Parse(string value, SourceEncoding sourceEncoding = SourceEncoding.Ascii, int decimalPlaces = 0)
{
var localValue = value; // Encoding.Convert(Encoding.ASCII, Encoding.GetEncoding("IBM037"), value.ToByteArray()).FromByteArray();
var sign = this.GetSign(localValue, out localValue);
var bytes = localValue.ToByteArray();
var nibbles = new List<int>();
var buffer = new StringBuilder();
foreach (var b in bytes)
{
var hi = (int)b.HiNibble();
var lo = (int)b.LoNibble();
nibbles.Add(hi);
nibbles.Add(lo);
buffer.AppendFormat("{0}{1}", hi, lo);
}
this.Bytes = bytes;
this.Nibbles = nibbles;
this.HexValues = nibbles.Select(v => v.ToString("X"));
switch (sign)
{
case Sign.Unsigned:
this.ParsedValue = buffer.ToString();
break;
case Sign.Positive:
this.ParsedValue = "+" + buffer;
break;
case Sign.Negative:
this.ParsedValue = "-" + buffer;
break;
}
this.IsPositive = sign == Sign.Positive;
this.IsNegative = sign == Sign.Negative;
return this.ParsedValue;
}
#region GetSign Method
/// <summary>
/// Gets the sign for the packed decimal represented by this instance.
/// </summary>
/// <param name="value">The value to analyze.</param>
/// <param name="buffer">Receives <paramref name="value"/>, less the sign digit if it is present.</param>
/// <returns>The sign for the packed decimal represented by this instance.</returns>
/// <remarks>If the value provided does not include a sign digit, it is assumed to be unsigned.</remarks>
private Sign GetSign(string value, out string buffer)
{
var lastDigit = value.ToByteArray().Last();
var loNibble = lastDigit.LoNibble();
var hiNibble = lastDigit.HiNibble();
var result = Sign.Unsigned;
var hasSignDigit = true;
switch (hiNibble)
{
case 0xC0: // "c"
result = Sign.Positive;
break;
case 0xD0: // "d"
result = Sign.Negative;
break;
case 0xF0: // "f"
result = Sign.Unsigned;
break;
default:
hasSignDigit = false;
break;
}
// Remove the sign digit if it's present.
buffer = hasSignDigit
? value.Substring(0, value.Length - 1) + loNibble
: value;
return result;
}
#endregion
#region Sign Enum
private enum Sign
{
Unsigned,
Positive,
Negative
}
#endregion
}
そして、それをサポートする拡張メソッド:
using System;
using System.Linq;
using System.Text;
public static class Extensions
{
/// <summary>
/// Gets the high nibble (the high 4 bits) from a byte.
/// </summary>
/// <param name="value">The byte from which the high 4-bit nibble will be retrieved.</param>
/// <returns>A byte containing the value of this byte, with all bits shifted four bits to the right.</returns>
public static byte HiNibble(this byte value)
{
return (byte)((value & 0xF0) >> 4);
}
/// <summary>
/// Gets the low nibble (the lowest 4 bits) from this byte.
/// </summary>
/// <param name="value">The byte from which the low 4-bit nibble will be retrieved.</param>
/// <returns>A byte containing the value of this byte, with the high four bits discarded.</returns>
public static byte LoNibble(this byte value)
{
return (byte)(value & 0x0F);
}
/// <summary>
/// Gets the individual bytes from a string.
/// </summary>
/// <param name="value">The string to convert to a byte array.</param>
/// <returns>An array of bytes representing the string.</returns>
public static byte[] ToByteArray(this string value)
{
var bytes = new byte[Encoding.ASCII.GetByteCount(value)];
Buffer.BlockCopy(value.ToCharArray(), 0, bytes, 0, bytes.Length);
return bytes;
}
}
public enum SourceEncoding
{
Ascii,
Ebcdic
}