Thursday, March 24, 2011

Most efficient way to convert BCD to binary

I have the code below to convert a 32 bit BCD value (supplied in two uint halves) to a uint binary value.

The values supplied can be up to 0x9999, to form a maximum value of 0x99999999.

Is there a better (ie. quicker) way to achieve this?

    /// <summary>
    /// Convert two PLC words in BCD format (forming 8 digit number) into single binary integer.
    /// e.g. If Lower = 0x5678 and Upper = 0x1234, then Return is 12345678 decimal, or 0xbc614e.
    /// </summary>
    /// <param name="lower">Least significant 16 bits.</param>
    /// <param name="upper">Most significant 16 bits.</param>
    /// <returns>32 bit unsigned integer.</returns>
    /// <remarks>If the parameters supplied are invalid, returns zero.</remarks>
    private static uint BCD2ToBin(uint lower, uint upper)
    {
        uint binVal = 0;

        if ((lower | upper) != 0)
        {
            int shift = 0;
            uint multiplier = 1;
            uint bcdVal = (upper << 16) | lower;

            for (int i = 0; i < 8; i++)
            {
                uint digit = (bcdVal >> shift) & 0xf;

                if (digit > 9)
                {
                    binVal = 0;
                    break;
                }
                else
                {
                    binVal += digit * multiplier;
                    shift += 4;
                    multiplier *= 10;
                }
            }
        }

        return binVal;
    }
From stackoverflow
  • Your code seems rather complicated; do you require the specific error checking?

    Otherwise, you could just use the following code which shouldn't be slower, in fact, it's mostly the same:

    uint result = 0;
    uint multiplier = 1;
    uint value = lo | hi << 0x10;
    
    while (value > 0) {
        uint digit = value & 0xF;
        value >>= 4;
        result += multiplier * digit;
        multiplier *= 10;
    }
    return result;
    
  • I suppose you could unroll the loop:

    value = ( lo     & 0xF);
    value+= ((lo>>4) & 0xF) *10;
    value+= ((lo>>8) & 0xF) *100;
    value+= ((lo>>12)& 0xF) *1000;
    value+= ( hi     & 0xF) *10000;
    value+= ((hi>>4  & 0xF) *100000;
    value+= ((hi>>8) & 0xF) *1000000;
    value+= ((hi>>12)& 0xF) *10000000;
    

    And you can check for invalid BCD digits like this:

    invalid = lo & ((lo&0x8888)>>2)*3
    

    This sets invalid to a non-zero value if any single hex digit > 9.

    epotter : This doesn't quite work. When you unroll the loop, you have to remember the "value >>= 4;"
  • If you unroll the loop, remember to keep the bit shift.

    value =  ( lo        & 0xF);
    value += ((lo >> 4 ) & 0xF) * 10;
    value += ((lo >> 8 ) & 0xF) * 100;
    value += ((lo >> 12) & 0xF) * 1000;
    value += ( hi        & 0xF) * 10000;
    value += ((hi >> 4 ) & 0xF) * 100000;
    value += ((hi >> 8 ) & 0xF) * 1000000;
    value += ((hi >> 12) & 0xF) * 10000000;
    
    epotter : This should be combined with AShelly's answer
    Adam Rosenfield : Please put parens around the bit twiddling operators - the operator precedence for them is very confusing for most people.
    epotter : Good call. That makes it much more readable.
  • If you've space to spare for a 39,322 element array, you could always just look the value up.

    ScottS : For raw speed a look-up table is certainly going to win.
    GvS : The quickest solution so far. (he asked for it)
  • Of course, there are a more efficient method. this is just a example of course, so you can tune it as a lesson ^^

    function bcd_to_bin ($bcd) {    
    $mask_sbb = 0x33333333;   
    $mask_msb = 0x88888888;
    $mask_opp = 0xF;
    
    for($i=28;$i;--$i) {   
     $mask_msb <<= 1;
     $mask_opp <<= 1;
     $mask_sbb <<= 1;
    
     for($j=0;$j<$i;$j+=4) { 
      $mask_opp_j = $mask_opp << $j;
    
      if ($bcd & $mask_msb & $mask_opp_j ) {
       $bcd -= $mask_sbb & $mask_opp_j;
      }
     }
    }
    
    return $bcd;
    

    }

0 comments:

Post a Comment