BCD (Binary Coded Decimals): Java (read-write) parsing library

BCD (Binary Coded Decimals) is a common way to encode integer numbers in a way that makes human-readable output somewhat simpler. In this encoding scheme, every decimal digit is encoded as either a single byte (8 bits), or a nibble (half of a byte, 4 bits). This obviously wastes a lot of bits, but it makes translation into human-readable string much easier than traditional binary-to-decimal conversion process, which includes lots of divisions by 10.

For example, encoding integer 31337 in 8-digit, 8 bits per digit, big endian order of digits BCD format yields

00 00 00 03 01 03 03 07

Encoding the same integer as 8-digit, 4 bits per digit, little endian order BCD format would yield:

73 31 30 00

Using this type of encoding in Kaitai Struct is pretty straightforward: one calls for this type, specifying desired encoding parameters, and gets result using either as_int or as_str attributes.

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.8

References

This page hosts a formal specification of BCD (Binary Coded Decimals) using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Java (read-write) source code to parse BCD (Binary Coded Decimals)

Bcd.java

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

import io.kaitai.struct.ByteBufferKaitaiStream;
import io.kaitai.struct.KaitaiStruct;
import io.kaitai.struct.KaitaiStream;
import java.io.IOException;
import java.util.ArrayList;
import io.kaitai.struct.ConsistencyError;
import java.util.List;


/**
 * BCD (Binary Coded Decimals) is a common way to encode integer
 * numbers in a way that makes human-readable output somewhat
 * simpler. In this encoding scheme, every decimal digit is encoded as
 * either a single byte (8 bits), or a nibble (half of a byte, 4
 * bits). This obviously wastes a lot of bits, but it makes translation
 * into human-readable string much easier than traditional
 * binary-to-decimal conversion process, which includes lots of
 * divisions by 10.
 * 
 * For example, encoding integer 31337 in 8-digit, 8 bits per digit,
 * big endian order of digits BCD format yields
 * 
 * ```
 * 00 00 00 03 01 03 03 07
 * ```
 * 
 * Encoding the same integer as 8-digit, 4 bits per digit, little
 * endian order BCD format would yield:
 * 
 * ```
 * 73 31 30 00
 * ```
 * 
 * Using this type of encoding in Kaitai Struct is pretty
 * straightforward: one calls for this type, specifying desired
 * encoding parameters, and gets result using either `as_int` or
 * `as_str` attributes.
 */
public class Bcd extends KaitaiStruct.ReadWrite {
    public Bcd(int numDigits, int bitsPerDigit, boolean isLe) {
        this(null, null, null, numDigits, bitsPerDigit, isLe);
    }

    public Bcd(KaitaiStream _io, int numDigits, int bitsPerDigit, boolean isLe) {
        this(_io, null, null, numDigits, bitsPerDigit, isLe);
    }

    public Bcd(KaitaiStream _io, KaitaiStruct.ReadWrite _parent, int numDigits, int bitsPerDigit, boolean isLe) {
        this(_io, _parent, null, numDigits, bitsPerDigit, isLe);
    }

    public Bcd(KaitaiStream _io, KaitaiStruct.ReadWrite _parent, Bcd _root, int numDigits, int bitsPerDigit, boolean isLe) {
        super(_io);
        this._parent = _parent;
        this._root = _root == null ? this : _root;
        this.numDigits = numDigits;
        this.bitsPerDigit = bitsPerDigit;
        this.isLe = isLe;
    }
    public void _read() {
        this.digits = new ArrayList<Integer>();
        for (int i = 0; i < numDigits(); i++) {
            switch (bitsPerDigit()) {
            case 4: {
                this.digits.add(((Number) (this._io.readBitsIntBe(4))).intValue());
                break;
            }
            case 8: {
                this.digits.add(((Number) (this._io.readU1())).intValue());
                break;
            }
            }
        }
        _dirty = false;
    }

    public void _fetchInstances() {
        for (int i = 0; i < this.digits.size(); i++) {
            switch (bitsPerDigit()) {
            case 4: {
                break;
            }
            case 8: {
                break;
            }
            }
        }
    }

    public void _write_Seq() {
        _assertNotDirty();
        for (int i = 0; i < this.digits.size(); i++) {
            switch (bitsPerDigit()) {
            case 4: {
                this._io.writeBitsIntBe(4, ((Number) (this.digits.get(((Number) (i)).intValue()))).longValue());
                break;
            }
            case 8: {
                this._io.writeU1(((Number) (this.digits.get(((Number) (i)).intValue()))).intValue());
                break;
            }
            }
        }
    }

    public void _check() {
        if (this.digits.size() != numDigits())
            throw new ConsistencyError("digits", numDigits(), this.digits.size());
        for (int i = 0; i < this.digits.size(); i++) {
            switch (bitsPerDigit()) {
            case 4: {
                break;
            }
            case 8: {
                break;
            }
            }
        }
        _dirty = false;
    }
    private Integer asInt;

    /**
     * Value of this BCD number as integer. Endianness would be selected based on `is_le` parameter given.
     */
    public Integer asInt() {
        if (this.asInt != null)
            return this.asInt;
        this.asInt = ((Number) ((isLe() ? asIntLe() : asIntBe()))).intValue();
        return this.asInt;
    }
    public void _invalidateAsInt() { this.asInt = null; }
    private Integer asIntBe;

    /**
     * Value of this BCD number as integer (treating digit order as big-endian).
     */
    public Integer asIntBe() {
        if (this.asIntBe != null)
            return this.asIntBe;
        this.asIntBe = ((Number) (digits().get(((Number) (lastIdx())).intValue()) + (numDigits() < 2 ? 0 : digits().get(((Number) (lastIdx() - 1)).intValue()) * 10 + (numDigits() < 3 ? 0 : digits().get(((Number) (lastIdx() - 2)).intValue()) * 100 + (numDigits() < 4 ? 0 : digits().get(((Number) (lastIdx() - 3)).intValue()) * 1000 + (numDigits() < 5 ? 0 : digits().get(((Number) (lastIdx() - 4)).intValue()) * 10000 + (numDigits() < 6 ? 0 : digits().get(((Number) (lastIdx() - 5)).intValue()) * 100000 + (numDigits() < 7 ? 0 : digits().get(((Number) (lastIdx() - 6)).intValue()) * 1000000 + (numDigits() < 8 ? 0 : digits().get(((Number) (lastIdx() - 7)).intValue()) * 10000000))))))))).intValue();
        return this.asIntBe;
    }
    public void _invalidateAsIntBe() { this.asIntBe = null; }
    private Integer asIntLe;

    /**
     * Value of this BCD number as integer (treating digit order as little-endian).
     */
    public Integer asIntLe() {
        if (this.asIntLe != null)
            return this.asIntLe;
        this.asIntLe = ((Number) (digits().get(((int) 0)) + (numDigits() < 2 ? 0 : digits().get(((int) 1)) * 10 + (numDigits() < 3 ? 0 : digits().get(((int) 2)) * 100 + (numDigits() < 4 ? 0 : digits().get(((int) 3)) * 1000 + (numDigits() < 5 ? 0 : digits().get(((int) 4)) * 10000 + (numDigits() < 6 ? 0 : digits().get(((int) 5)) * 100000 + (numDigits() < 7 ? 0 : digits().get(((int) 6)) * 1000000 + (numDigits() < 8 ? 0 : digits().get(((int) 7)) * 10000000))))))))).intValue();
        return this.asIntLe;
    }
    public void _invalidateAsIntLe() { this.asIntLe = null; }
    private Integer lastIdx;

    /**
     * Index of last digit (0-based).
     */
    public Integer lastIdx() {
        if (this.lastIdx != null)
            return this.lastIdx;
        this.lastIdx = ((Number) (numDigits() - 1)).intValue();
        return this.lastIdx;
    }
    public void _invalidateLastIdx() { this.lastIdx = null; }
    private List<Integer> digits;
    private int numDigits;
    private int bitsPerDigit;
    private boolean isLe;
    private Bcd _root;
    private KaitaiStruct.ReadWrite _parent;
    public List<Integer> digits() { return digits; }
    public void setDigits(List<Integer> _v) { _dirty = true; digits = _v; }

    /**
     * Number of digits in this BCD representation. Only values from 1 to 8 inclusive are supported.
     */
    public int numDigits() { return numDigits; }
    public void setNumDigits(int _v) { _dirty = true; numDigits = _v; }

    /**
     * Number of bits per digit. Only values of 4 and 8 are supported.
     */
    public int bitsPerDigit() { return bitsPerDigit; }
    public void setBitsPerDigit(int _v) { _dirty = true; bitsPerDigit = _v; }

    /**
     * Endianness used by this BCD representation. True means little-endian, false is for big-endian.
     */
    public boolean isLe() { return isLe; }
    public void setIsLe(boolean _v) { _dirty = true; isLe = _v; }
    public Bcd _root() { return _root; }
    public void set_root(Bcd _v) { _dirty = true; _root = _v; }
    public KaitaiStruct.ReadWrite _parent() { return _parent; }
    public void set_parent(KaitaiStruct.ReadWrite _v) { _dirty = true; _parent = _v; }
}