BCD Codec

easy · iso8583, payments

BCD Codec

You're building a parser for a legacy payment network that uses BCD (Binary-Coded Decimal) encoding. Your team lead hands you a hex dump and says: "The amount field is 6 bytes starting at offset 20, but it's BCD-encoded. Decode it."

You stare at the bytes: 00 00 01 25 50 00

That's not ASCII. Each byte contains two decimal digits packed together. Welcome to BCD.

What is BCD?

BCD packs two decimal digits into a single byte. Each digit (0-9) uses 4 bits (a "nibble"):

Digit  Binary  |  Digit  Binary
  0    0000    |    5    0101
  1    0001    |    6    0110
  2    0010    |    7    0111
  3    0011    |    8    1000
  4    0100    |    9    1001

Two nibbles per byte:

Byte 0x47:
  Binary: 0100 0111
          ↓    ↓
          4    7
  Decoded: "47"

Byte 0x12:
  Binary: 0001 0010
          ↓    ↓
          1    2
  Decoded: "12"

So 00 00 01 25 50 00 decodes to "000001255000".

The Challenge

Implement two functions:

  1. EncodeBCD: Convert a numeric string to BCD bytes
  2. DecodeBCD: Convert BCD bytes back to a numeric string

What You're Building

// EncodeBCD converts a numeric string to packed BCD bytes.
//
// For odd-length strings, left-pad with '0' before encoding.
// For example: "123" becomes "0123" → 0x01, 0x23
//
// Returns an error if the input contains non-digit characters.
func EncodeBCD(s string) ([]byte, error)

// DecodeBCD converts packed BCD bytes to a numeric string.
//
// Each byte produces exactly 2 digits. Leading zeros are preserved.
// For example: 0x01, 0x23 → "0123"
//
// Returns an error if any nibble is > 9 (invalid BCD).
func DecodeBCD(data []byte) (string, error)

Behavior

EncodeBCD

  • Input: String of digits ("0"-"9" only)
  • Output: BCD-encoded bytes
  • Empty string → empty slice (no error)
  • Odd-length strings: Left-pad with '0' first
    • "5" → "05" → 0x05
    • "123" → "0123" → 0x01, 0x23
  • Non-digit character → error

DecodeBCD

  • Input: BCD-encoded bytes
  • Output: Numeric string (always even length)
  • Empty slice → empty string (no error)
  • Each byte produces exactly 2 digits
  • Invalid nibble (> 9) → error

Examples

// Basic encoding
EncodeBCD("1234")
// → []byte{0x12, 0x34}, nil

// Odd length - left-padded
EncodeBCD("123")
// → []byte{0x01, 0x23}, nil

// Single digit
EncodeBCD("5")
// → []byte{0x05}, nil

// Amount encoding (common use case)
EncodeBCD("000000010000")  // $100.00 in cents
// → []byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x00}, nil

// Empty string
EncodeBCD("")
// → []byte{}, nil

// Invalid input
EncodeBCD("12A4")
// → nil, error("invalid character 'A' at position 2")

// Basic decoding
DecodeBCD([]byte{0x12, 0x34})
// → "1234", nil

// Preserves leading zeros
DecodeBCD([]byte{0x00, 0x12})
// → "0012", nil

// Invalid BCD (nibble > 9)
DecodeBCD([]byte{0xAB})
// → "", error("invalid BCD nibble 10 in byte 0")

// Empty slice
DecodeBCD([]byte{})
// → "", nil

Edge Cases

  1. All zeros: EncodeBCD("0000")0x00, 0x00
  2. All nines: EncodeBCD("9999")0x99, 0x99
  3. Leading zeros preserved: DecodeBCD(0x00, 0x01)"0001" (not "1")
  4. Single byte: Both 0x00 and 0x99 are valid

Why This Matters

BCD is everywhere in financial systems:

  • Amount fields: Many networks transmit amounts in BCD to save bandwidth
  • Length prefixes: Some LLVAR implementations use BCD lengths
  • Track data: Portions of track 2 data use BCD
  • PIN blocks: Part of the PIN block format includes BCD-encoded data
  • Date/time fields: Legacy systems often BCD-encode dates

When you see a 6-byte field that should contain a 12-digit amount, but the bytes look like 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 instead of ASCII "000001000000" (which would be 12 bytes), you're looking at BCD.

Hints (only if stuck)

<details> <summary>Hint 1: Extracting nibbles</summary>

To get the high nibble (first digit): (byte >> 4) & 0x0F To get the low nibble (second digit): byte & 0x0F

</details>

<details> <summary>Hint 2: Combining nibbles</summary>

To pack two digits into one byte: (highDigit << 4) | lowDigit

</details>

<details> <summary>Hint 3: Digit to nibble</summary>

If you have a character '5' (ASCII 0x35), the numeric value is '5' - '0' = 5.

</details>

Run tests to see results
No issues detected
    Join Discord