Skip to content

BCD Converter

Convert decimal to packed and unpacked binary-coded decimal and back, with invalid-nibble detection.

Direction
Packed BCD20 26
Unpacked BCD02 00 02 06
Binary nibbles0010 0000 0010 0110
Packed bytes2 bytes

BCD vs plain binary

Packed BCD, read as hex0x2026
Same number in plain binary0x7EA

These differ, and that difference is the whole point. In BCD the hex reading looks like the decimal number - 2026 stores as 0x2026. Read that register as a plain integer instead and you get 8230, which is the classic real-time-clock bug.

Excess-3 (Stibitz)0101 0011 0101 1001
Aiken 24210010 0000 0010 1100

Digit encoding reference

DecimalBCD (8421)Excess-3Aiken 2421
0000000110000
1000101000001
2001001010010
3001101100011
4010001110100
5010110001011
6011010011100
7011110101101
8100010111110
9100111001111
invalid1010 (A)--
invalid1011 (B)--
invalid1100 (C)--
invalid1101 (D)--
invalid1110 (E)--
invalid1111 (F)--

Six of the sixteen nibble values are unused in BCD, which is where the roughly 20% storage overhead comes from. A byte holds 0-99 in packed BCD against 0-255 in plain binary.

Packed versus unpacked

Packed BCD puts two digits in a byte: 1234 becomes the two bytes 12 34. Unpacked BCD gives each digit its own byte with the high nibble zeroed, so the same number is 01 02 03 04 - four bytes. Packed halves the storage and is what hardware registers use; unpacked is easier to index, easier to print, and what you convert to before driving a display.

An odd digit count has to be padded, because a byte holds two nibbles. The convention is a leading zero nibble, so 123 packs as 01 23. Some financial formats instead use the low nibble of the last byte as a sign, which is why COBOL packed-decimal fields end in 0x0C for positive and 0x0D for negative.

Why BCD survives

BCD is less efficient than binary and slower to do arithmetic in, and it is still everywhere. Real-time clock chips - the DS1307, DS3231, PCF8523 - report seconds, minutes, hours and date as BCD registers, because the part drives a display more often than it does arithmetic and BCD makes digit extraction a shift and a mask. Seven-segment decoder ICs take BCD directly.

The other reason is exactness. Decimal fractions like 0.1 have no finite binary representation, so a binary float accumulates error in a way that shows up on a bank statement. Decimal-coded formats do not, which is why IBM mainframe packed decimal, IEEE 754 decimal floating point and every serious money type are decimal underneath.

The cost is arithmetic. Adding two BCD numbers as binary can produce a nibble above 9, so hardware needs a decimal-adjust step: add 6 to any nibble that overflowed. x86 exposed this as the DAA instruction; on ARM you do it in software, which is a large part of why BCD arithmetic is avoided outside the domains that need it.

The classic RTC bug

You read the seconds register of a DS1307 and get 0x25. Treat that as a plain integer and your clock says 37 seconds. The register is BCD: the high nibble is the tens digit and the low nibble is the units, so the value is 25.

uint8_t raw = i2c_read(DS1307_SECONDS);   /* 0x25 */
uint8_t wrong = raw;                       /* 37 - reads as binary */
uint8_t right = (raw >> 4) * 10 + (raw & 0x0F);  /* 25 */

The tell is that the wrong value is always larger than the right one, and only ever by a multiple of six per tens digit. If a clock drifts by exactly 6 seconds per 10, this is why.

Code Examples

#include <stdint.h>

/* Packed BCD byte -> binary, the RTC read path */
uint8_t bcd_to_bin(uint8_t bcd) {
    return (uint8_t)((bcd >> 4) * 10 + (bcd & 0x0F));
}

/* Binary -> packed BCD, the RTC write path */
uint8_t bin_to_bcd(uint8_t bin) {
    return (uint8_t)(((bin / 10) << 4) | (bin % 10));
}

/* Validate before trusting a nibble */
int bcd_valid(uint8_t bcd) {
    return (bcd >> 4) <= 9 && (bcd & 0x0F) <= 9;
}

Frequently Asked Questions

What is BCD?

Binary-coded decimal stores each decimal digit in its own four-bit nibble instead of converting the whole number to binary. The decimal 25 is 0x25 in packed BCD, not 0x19 - the digits are preserved literally, which is why the hex reading of a BCD field looks like the decimal number.

What is the difference between packed and unpacked BCD?

Packed BCD puts two digits in one byte, so 1234 is 12 34 - two bytes. Unpacked BCD gives each digit its own byte with the high nibble zeroed, so 1234 is 01 02 03 04 - four bytes. Packed halves the storage; unpacked is easier to index and print.

Why is BCD still used?

Real-time clock chips (DS1307, PCF8523) report time in BCD, seven-segment display drivers take BCD directly, and financial and metering formats use it so decimal fractions round the way an accountant expects rather than the way binary floating point does.

Why did my clock read 25 as 37?

That is the classic RTC bug: you read a BCD register as a plain binary integer. 0x25 as binary is 37 decimal. Convert with value = (raw >> 4) * 10 + (raw & 0x0F) instead - the same arithmetic this page performs.

What makes a BCD nibble invalid?

Anything from A to F. Only 0000 through 1001 are legal BCD digits, so six of the sixteen nibble values are unused. This converter flags them rather than silently decoding them, because a stray A in a BCD field is a symptom of reading the wrong register or a corrupted frame.

How much space does BCD waste?

A nibble holds 16 values and BCD uses 10, so about 20% of the bits are unused. A byte holds 0-99 in packed BCD versus 0-255 in binary. That overhead buys you digit-exact storage and trivial digit extraction.

What is Excess-3?

A BCD variant where each digit is stored as digit + 3, so 0 is 0011 and 9 is 1100. It is self-complementing - the nine's complement of a digit is the bitwise complement of its code - which made subtraction cheap in early decimal hardware.

What is Aiken 2421 code?

Another self-complementing decimal code, weighted 2-4-2-1 instead of 8-4-2-1. Like Excess-3 it makes nine's complement a bitwise NOT, but unlike Excess-3 it keeps zero as 0000.

How do I add two BCD numbers?

Add them as binary, then apply a decimal adjust: if a nibble exceeds 9 or produced a carry, add 6 to it. x86 has DAA for exactly this and ARM expects you to do it in software. It is why BCD arithmetic is slower than binary.

Related Tools

This tool runs entirely in your browser - nothing you enter is uploaded or stored.