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)

Hint 1: Extracting nibbles

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

Hint 2: Combining nibbles

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

Hint 3: Digit to nibble

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

Run tests to see results
No issues detected
    Join Discord