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 kaitai.Struct
	_f_asInt bool
	asInt int
	_f_asIntBe bool
	asIntBe int
	_f_asIntLe bool
	asIntLe int
	_f_lastIdx bool
	lastIdx int
}
func NewBcd(numDigits uint8, bitsPerDigit uint8, isLe bool) *Bcd {
	return &Bcd{
		NumDigits: numDigits,
		BitsPerDigit: bitsPerDigit,
		IsLe: isLe,
	}
}

func (this Bcd) IO_() *kaitai.Stream {
	return this._io
}

func (this *Bcd) Read(io *kaitai.Stream, parent kaitai.Struct, 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
	}
	this._f_asInt = true
	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)
	return this.asInt, 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
	}
	this._f_asIntBe = true
	tmp6, err := this.LastIdx()
	if err != nil {
		return 0, err
	}
	var tmp7 int8;
	if (this.NumDigits < 2) {
		tmp7 = 0
	} else {
		tmp8, err := this.LastIdx()
		if err != nil {
			return 0, err
		}
		var tmp9 int8;
		if (this.NumDigits < 3) {
			tmp9 = 0
		} else {
			tmp10, err := this.LastIdx()
			if err != nil {
				return 0, err
			}
			var tmp11 int8;
			if (this.NumDigits < 4) {
				tmp11 = 0
			} else {
				tmp12, err := this.LastIdx()
				if err != nil {
					return 0, err
				}
				var tmp13 int8;
				if (this.NumDigits < 5) {
					tmp13 = 0
				} else {
					tmp14, err := this.LastIdx()
					if err != nil {
						return 0, err
					}
					var tmp15 int8;
					if (this.NumDigits < 6) {
						tmp15 = 0
					} else {
						tmp16, err := this.LastIdx()
						if err != nil {
							return 0, err
						}
						var tmp17 int8;
						if (this.NumDigits < 7) {
							tmp17 = 0
						} else {
							tmp18, err := this.LastIdx()
							if err != nil {
								return 0, err
							}
							var tmp19 int8;
							if (this.NumDigits < 8) {
								tmp19 = 0
							} else {
								tmp20, err := this.LastIdx()
								if err != nil {
									return 0, err
								}
								tmp19 = this.Digits[tmp20 - 7] * 10000000
							}
							tmp17 = this.Digits[tmp18 - 6] * 1000000 + tmp19
						}
						tmp15 = this.Digits[tmp16 - 5] * 100000 + tmp17
					}
					tmp13 = this.Digits[tmp14 - 4] * 10000 + tmp15
				}
				tmp11 = this.Digits[tmp12 - 3] * 1000 + tmp13
			}
			tmp9 = this.Digits[tmp10 - 2] * 100 + tmp11
		}
		tmp7 = this.Digits[tmp8 - 1] * 10 + tmp9
	}
	this.asIntBe = int(this.Digits[tmp6] + tmp7)
	return this.asIntBe, 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
	}
	this._f_asIntLe = true
	var tmp21 int8;
	if (this.NumDigits < 2) {
		tmp21 = 0
	} else {
		var tmp22 int8;
		if (this.NumDigits < 3) {
			tmp22 = 0
		} else {
			var tmp23 int8;
			if (this.NumDigits < 4) {
				tmp23 = 0
			} else {
				var tmp24 int8;
				if (this.NumDigits < 5) {
					tmp24 = 0
				} else {
					var tmp25 int8;
					if (this.NumDigits < 6) {
						tmp25 = 0
					} else {
						var tmp26 int8;
						if (this.NumDigits < 7) {
							tmp26 = 0
						} else {
							var tmp27 int8;
							if (this.NumDigits < 8) {
								tmp27 = 0
							} else {
								tmp27 = this.Digits[7] * 10000000
							}
							tmp26 = this.Digits[6] * 1000000 + tmp27
						}
						tmp25 = this.Digits[5] * 100000 + tmp26
					}
					tmp24 = this.Digits[4] * 10000 + tmp25
				}
				tmp23 = this.Digits[3] * 1000 + tmp24
			}
			tmp22 = this.Digits[2] * 100 + tmp23
		}
		tmp21 = this.Digits[1] * 10 + tmp22
	}
	this.asIntLe = int(this.Digits[0] + tmp21)
	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._f_lastIdx = true
	this.lastIdx = int(this.NumDigits - 1)
	return this.lastIdx, nil
}