## Friday, March 14, 2008

### Binary-Coded Decimal

Introduction

For the EARS project I needed to interface to some custom built transmitter hardware and to telecoms equipment. Both of these systems wanted to receive numeric data in BCD, or Binary-Coded Decimal, format. And if your computer engineering knowledge is as old, rusty, and forgotten as mine, a lot of review is in order.

The Basics

A byte is typically 8 bits and an 8 bit byte is called an octet. A byte can be divided two 4 bit nibbles each of which contains a single hexadecimal digit. Where a hexadecimal digit is a numeric value in base 16 and represents a decimal value of 0 to 15 or 0x0 to 0xf in hex notation, and a byte is represented as two hex digits. A BCD value then is a binary representation of a decimal number. So a BCD representation of the number 129 may be 0x129, which consists of three hex digits one for each decimal digit. The straight binary representation takes only a single byte and would be the hex value 0x81 or the binary value of 10000001. A BCD value may be unpacked in which each decimal digit takes an entire byte, or packed in which each decimal digit takes one nibble. So the unpacked representation of 129 takes 3 bytes and is the value 0x010209. The packed BCD value will only take two bytes and is the value 0x0129.

Converting

The goal is to convert a string of numbers into its BCD representation. It is very easy to convert an ASCII numeric character to its hexadecimal equivalent. From the table of ASCII characters we see that the character '0' is the hex value 0x30 and '9' is the value 0x39. So we just need to subtract 0x30 from each numeric character to get its binary representation. This is of course easy if one uses the appropriate programming language, which in all cases where one needs portability, performance, and maximum bit twiddling functionality, is C.

A simple search with your favorite search engine will result in many pages of information and example code. As most of my work is required to fit in the mythical realm of 5 9s I require a bit more robustness than you will probably find in your search. So my implementation is as follows.

Prototypes

Since I only need to go from a string to BCD I wrote two functions, a2ubcd to convert to unpacked BCD, and a2pbcd to convert to packed BCD. The prototypes of the functions are:

int a2ubcd( const char *str, uint_8 *bcd, size_t sz );
int a2pbcd( const char *str, uint_8 *bcd, size_t sz );

The function comments are basically also identical so following is the comment for the a2ubcd function.

Convert a string to an unpacked BCD array.
Converts the numbers in the string to an unpacked BCD number.

The BCD number is represented as an array of bytes with each byte being one digit. The LSD of the number will be the last element of the array, and if the BCD array is larger than the number of digits the BCD number will be padded with leading 0s.

The string to convert must be a null terminated string with the first character being a number. The conversion will start at the last numeric character found. If the BCD array is not large enough to hold the number it is truncated and an error is returned.

@param str The string to convert to BCD.
@param bcd The array to hold the BCD number.
@param sz The length of bcd.

@returns RET_OK on success.
RET_INVALID_PARAM if str or bcd is NULL, sz <= 0, or if the first character of str is not a numeric. RET_OUT_OF_RANGE if bcd is not large enough to hold the number.

The return values are obviously defined constants. The uint_8 type is an unsigned 8bit integer, which is most likely an unsigned short.

Implementation

As much as I would like to just paste my code in I do not have the rights to do so. Hopefully, the pseudo-coded description will suffice.

function a2ubcd
receives: char *str, uint_8 *bcd, size_t sz
returns: status code

begin
if str or bcd is NULL return invalid param
if sz <= 0 return invalid param
if the first character of str is not a number
return invalid param

set currChar to the first character of str
set nextChar to the second character of str

while nextChar is not NULL and nextChar is a number
set currChar to nextChar
set nextChar to the next character of str
end while

set currDigit to the last element of bcd
while currDigit is an element of bcd
if currChar is an element of str
set currDigit to currChar - 0x30
set currChar to the previous character of str
else
set currDigit to 0
end if
end while

if currChar is an element of str
return out of range
else
return OK
end

The packed BCD conversion function is mostly identical to the unpacked version. The differences being in the while loop that sets the bcd values. The packed version of this loop is.

set currDigit to the last element of bcd

while currDigit is an element of bcd
set highNibble to '0'
set lowNibble to '0'
if currChar is an element of str
set lowNibble to currChar
set currChar to the previous character of str
end if

if currChar is an element of str
set highNibble to currChar
set currChar to the previous character of str
end if

set currDigit to ((highNibble - 0x30) shift left 4) + lowNibble - 0x30
end while