BCD (Binary Coded Decimals): Go 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.

Go source code to parse BCD (Binary Coded Decimals)

bcd.go

// Code generated by kaitai-struct-compiler from a .ksy source file. DO NOT EDIT.

import "github.com/kaitai-io/kaitai_struct_go_runtime/kaitai"


/**
 * 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.
 */
type Bcd struct {
	Digits []int
	NumDigits uint8
	BitsPerDigit uint8
	IsLe bool
	_io *kaitai.Stream
	_root *Bcd
	_parent interface{}
	_f_asInt bool
	asInt int
	_f_asIntLe bool
	asIntLe int
	_f_lastIdx bool
	lastIdx int
	_f_asIntBe bool
	asIntBe int
}
func NewBcd(numDigits uint8, bitsPerDigit uint8, isLe bool) *Bcd {
	return &Bcd{
		NumDigits: numDigits,
		BitsPerDigit: bitsPerDigit,
		IsLe: isLe,
	}
}

func (this *Bcd) Read(io *kaitai.Stream, parent interface{}, root *Bcd) (err error) {
	this._io = io
	this._parent = parent
	this._root = root

	for i := 0; i < int(this.NumDigits); i++ {
		_ = i
		switch (this.BitsPerDigit) {
		case 4:
			tmp1, err := this._io.ReadBitsIntBe(4)
			if err != nil {
				return err
			}
			this.Digits = append(this.Digits, tmp1)
		case 8:
			tmp2, err := this._io.ReadU1()
			if err != nil {
				return err
			}
			this.Digits = append(this.Digits, tmp2)
		}
	}
	return err
}

/**
 * Value of this BCD number as integer. Endianness would be selected based on `is_le` parameter given.
 */
func (this *Bcd) AsInt() (v int, err error) {
	if (this._f_asInt) {
		return this.asInt, nil
	}
	var tmp3 int;
	if (this.IsLe) {
		tmp4, err := this.AsIntLe()
		if err != nil {
			return 0, err
		}
		tmp3 = tmp4
	} else {
		tmp5, err := this.AsIntBe()
		if err != nil {
			return 0, err
		}
		tmp3 = tmp5
	}
	this.asInt = int(tmp3)
	this._f_asInt = true
	return this.asInt, nil
}

/**
 * Value of this BCD number as integer (treating digit order as little-endian).
 */
func (this *Bcd) AsIntLe() (v int, err error) {
	if (this._f_asIntLe) {
		return this.asIntLe, nil
	}
	var tmp6 int8;
	if (this.NumDigits < 2) {
		tmp6 = 0
	} else {
		var tmp7 int8;
		if (this.NumDigits < 3) {
			tmp7 = 0
		} else {
			var tmp8 int8;
			if (this.NumDigits < 4) {
				tmp8 = 0
			} else {
				var tmp9 int8;
				if (this.NumDigits < 5) {
					tmp9 = 0
				} else {
					var tmp10 int8;
					if (this.NumDigits < 6) {
						tmp10 = 0
					} else {
						var tmp11 int8;
						if (this.NumDigits < 7) {
							tmp11 = 0
						} else {
							var tmp12 int8;
							if (this.NumDigits < 8) {
								tmp12 = 0
							} else {
								tmp12 = (this.Digits[7] * 10000000)
							}
							tmp11 = ((this.Digits[6] * 1000000) + tmp12)
						}
						tmp10 = ((this.Digits[5] * 100000) + tmp11)
					}
					tmp9 = ((this.Digits[4] * 10000) + tmp10)
				}
				tmp8 = ((this.Digits[3] * 1000) + tmp9)
			}
			tmp7 = ((this.Digits[2] * 100) + tmp8)
		}
		tmp6 = ((this.Digits[1] * 10) + tmp7)
	}
	this.asIntLe = int((this.Digits[0] + tmp6))
	this._f_asIntLe = true
	return this.asIntLe, nil
}

/**
 * Index of last digit (0-based).
 */
func (this *Bcd) LastIdx() (v int, err error) {
	if (this._f_lastIdx) {
		return this.lastIdx, nil
	}
	this.lastIdx = int((this.NumDigits - 1))
	this._f_lastIdx = true
	return this.lastIdx, nil
}

/**
 * Value of this BCD number as integer (treating digit order as big-endian).
 */
func (this *Bcd) AsIntBe() (v int, err error) {
	if (this._f_asIntBe) {
		return this.asIntBe, nil
	}
	tmp13, err := this.LastIdx()
	if err != nil {
		return 0, err
	}
	var tmp14 int8;
	if (this.NumDigits < 2) {
		tmp14 = 0
	} else {
		tmp15, err := this.LastIdx()
		if err != nil {
			return 0, err
		}
		var tmp16 int8;
		if (this.NumDigits < 3) {
			tmp16 = 0
		} else {
			tmp17, err := this.LastIdx()
			if err != nil {
				return 0, err
			}
			var tmp18 int8;
			if (this.NumDigits < 4) {
				tmp18 = 0
			} else {
				tmp19, err := this.LastIdx()
				if err != nil {
					return 0, err
				}
				var tmp20 int8;
				if (this.NumDigits < 5) {
					tmp20 = 0
				} else {
					tmp21, err := this.LastIdx()
					if err != nil {
						return 0, err
					}
					var tmp22 int8;
					if (this.NumDigits < 6) {
						tmp22 = 0
					} else {
						tmp23, err := this.LastIdx()
						if err != nil {
							return 0, err
						}
						var tmp24 int8;
						if (this.NumDigits < 7) {
							tmp24 = 0
						} else {
							tmp25, err := this.LastIdx()
							if err != nil {
								return 0, err
							}
							var tmp26 int8;
							if (this.NumDigits < 8) {
								tmp26 = 0
							} else {
								tmp27, err := this.LastIdx()
								if err != nil {
									return 0, err
								}
								tmp26 = (this.Digits[(tmp27 - 7)] * 10000000)
							}
							tmp24 = ((this.Digits[(tmp25 - 6)] * 1000000) + tmp26)
						}
						tmp22 = ((this.Digits[(tmp23 - 5)] * 100000) + tmp24)
					}
					tmp20 = ((this.Digits[(tmp21 - 4)] * 10000) + tmp22)
				}
				tmp18 = ((this.Digits[(tmp19 - 3)] * 1000) + tmp20)
			}
			tmp16 = ((this.Digits[(tmp17 - 2)] * 100) + tmp18)
		}
		tmp14 = ((this.Digits[(tmp15 - 1)] * 10) + tmp16)
	}
	this.asIntBe = int((this.Digits[tmp13] + tmp14))
	this._f_asIntBe = true
	return this.asIntBe, nil
}