Monday 17 November 2008

BCD to Binary conversion

I needed to convert some numbers received from a PLC from their original packed BCD format to binary (hex) format. The numbers to convert are split into two 16-bit halves, and need to be converted into one 32-bit number.

(Packed BCD values contain two digits for every byte.)

There is a method that exists that will convert numbers from BCD into a true hex number, by using string formatting, but this is relatively expensive in terms of performance and I required the best performance possible.

So, I came up with this function:

   1:  /// <summary>
   2:  /// Convert two PLC words in BCD format (forming 8 digit number) into single binary integer.
   3:  /// e.g. If Lower = 0x5678 and Upper = 0x1234, then Return is 12345678 decimal, or 0xbc614e.
   4:  /// </summary>
   5:  /// <param name="lower">Least significant 16 bits.</param>
   6:  /// <param name="upper">Most significant 16 bits.</param>
   7:  /// <returns>32 bit unsigned integer.</returns>
   8:  /// <remarks>If the parameters supplied are invalid, returns zero.</remarks>
   9:  private uint BCD2ToBin(uint lower, uint upper)
  10:  {
  11:      uint binVal = 0;
  12:   
  13:      if ((lower | upper) != 0)
  14:      {
  15:          int shift = 0;
  16:          uint multiplier = 1;
  17:          uint bcdVal = (upper << 16) | lower;
  18:   
  19:          for (int i = 0; i < 8; i++)
  20:          {
  21:              uint digit = (bcdVal >> shift) & 0xf;
  22:   
  23:              if (digit > 9)
  24:              {
  25:                  binVal = 0;
  26:                  break;
  27:              }
  28:              else
  29:              {
  30:                  binVal += digit * multiplier;
  31:                  shift += 4;
  32:                  multiplier *= 10;
  33:              }
  34:          }
  35:      }
  36:   
  37:      return binVal;
  38:  }


The function takes in two unsigned itegers that hold each of the 16-bit weighted BCD values, and returns a 32-bit unsigned integer.

It is important to note that the return is an unsigned integer, as this allows BCD values up to 0x99999999 to be converted correctly.

I have also taken the approach of returning zero if any of the values supplied are incorrect, i.e. contain an invalid digit. (BCD values can only contain digits ranging between 0 and 9, and not A to F.)

Of course, it may be desirable to return a different value, or to throw an exception if the parameters supplied are invalid, but for my purposes, returning a zero was sufficient.

This method proved to be around four to five times quicker than by using the string format method.