3

I am shifting the bits of a BCD number either left or right to quickly multiply or divide by 2. Here is a quick example of shifting left:

void LShift(unsigned char *arg)
{
   int i, carry=0, temp;
   for(i=2;i>=0;i--)
   {
      temp=(arg[i]<<1)+carry;
      if (temp>9) temp+=6;
      carry=temp>>4;
      arg[i]=temp&0xF;
   }
}

This works fine and if you give it an array like {4,5,6} it will return {9,1,2}. The problem is that if I want to shift by more than one bit, I have to call the function over and over. Is there any clever way to shift by more than one bit at a time without converting the BCD number to decimal first?

Mat
  • 202,337
  • 40
  • 393
  • 406
  • 1
    If this is not C, please [edit] your question to put the right language tag. Never forget the language tag, it's the most important one. – Mat Jul 23 '13 at 05:00
  • 2
  • How does the function handle `{9,1,2}` as input? You need an extra digit (byte) of output? Generalizing, you'll need possibly many extra bytes for the output value for shifts larger than 1. You can generalize the interface: `size_t BCD_LShift(unsigned char *value, size_t vallen, size_t lshift, unsigned char *result, size_t reslen);` where the return value is the number of BCD digits in the result. I wonder where the break-point is between repeated invocation of the function and 'convert to binary integer, shift, convert to BCD' is? – Jonathan Leffler Jul 23 '13 at 05:32
  • A single digit 4 << 2 in BCD gives 10, which is clearly wrong, as it should be (0x)16. This is only achievable by the logic, where the adjustment is done on every stage: 4 << 1 = 8, 8<<1 = (8 + 3) << 1 = 16. – Aki Suihkonen Jul 23 '13 at 05:39
  • Thanks for your comments guys. @AkiSuihkonen, that is what I feared. It looks like there may be no solution. – Joey Shepard Jul 23 '13 at 05:48

3 Answers3

3

See below, N being the number of bits to shift, assuming N<=3, if you shift by more than 3 (N>3), you'll need to handle more than one digit of carry (e.g. 9*(2^4) = 144):

void LShift(unsigned char *arg)
{
   int i, carry=0, temp;
   for(i=2;i>=0;i--)
   {
      temp=(arg[i]<<N)+carry; 
      arg[i]=temp%10;
      temp -= arg[i];
      carry = temp/10;
   }
}

Or if you want something closer to the original:

void LShift(unsigned char *arg)
{
   int i, carry=0, temp;
   for(i=2;i>=0;i--)
   {
      temp=(arg[i]<<N)+carry;
      temp+=6 * (temp/10);
      carry=temp>>4;
      arg[i]=temp&0xF;
   }
}

Also note that (in all versions, including the original) you may be left with carry which is a new digit.

Ofir
  • 8,194
  • 2
  • 29
  • 44
  • This doesn't work. In BCD one has to adjust digits > 4 (by 3) on every stage. – Aki Suihkonen Jul 23 '13 at 05:33
  • 1
    The problem is not the shifting, its the BCD correction (the line `if (temp>9) temp += 6;`, which only works for 1-bit shifts) – Chris Dodd Jul 23 '13 at 05:40
  • Right, missed the fact the the program depends on carry never being more than 1, corrected – Ofir Jul 23 '13 at 06:17
  • @Ofir Thanks. This looks like what I was looking for. Is there no way to get around the division? I'm working with a microcontroller with no hardware support for multiplication or division which is why I am shifting in the first place. That said, even with that division it is probably faster than calling the function three times like I was doing. – Joey Shepard Jul 23 '13 at 06:30
  • It is possible to get around the division using a lookup table (I'm not sure if that is better for your purpose) – Ofir Jul 23 '13 at 06:34
1

You have to redesign the function to include the number of bits to shift as an argument.And use that argument to shift the bcd byte a number of places .

void LShift(unsigned char *arg) can be modified to void LShift(unsigned char *arg,int n)

and do

temp=(arg[i]<<n)+carry;
Santhosh Pai
  • 2,535
  • 8
  • 28
  • 49
0

Coding up the shift logic is a little complicated, as the carry manipulation is a little hairy. It turns into code that looks very much like a shift and add multiplication implementation.

void LeftShiftN_1 (unsigned char arg[BCD_DIGITS], unsigned N) {
    int i, j;
    unsigned x, carry;
    unsigned char accum[BCD_DIGITS];
    memset(accum, '\0', sizeof(carry));
    for (i=BCD_DIGITS; i>0; --i) {
        x = arg[i-1];
        x <<= N;
        carry = 0;
        for (j=i; j>=0; --j) {
            carry += accum[j-1] + x % 10;
            x /= 10;
            accum[j-1] = carry % 10;
            carry /= 10;
        }
    }
    memcpy(arg, accum, sizeof(accum));
}

I think it is much simpler and efficient to convert from BCD and back again to do the shift. I implemented in a straightforward way, I am sure the conversion operations could be optimized.

void LeftShiftN_2 (unsigned char arg[BCD_DIGITS], unsigned N) {
    int i;
    unsigned accum;
    accum = 0;
    for (i=0; i<BCD_DIGITS; ++i) {
        accum = 10*accum + arg[i];
    }
    accum <<= N;
    for (i=BCD_DIGITS; i>0; --i) {
        arg[i-1] = accum % 10;
        accum /= 10;
    }
}
jxh
  • 69,070
  • 8
  • 110
  • 193
  • Yes, maybe just converting to decimal is better in that case. Doing any kind of multiplication or division can bog down the code since they will be translated into repeated adds. I am using the bit shifting to do CORDIC routines so I need them to be as fast as possible. It seems, though, that multiplication and division will be necessary. – Joey Shepard Jul 23 '13 at 11:43
  • If you really only have 3 BCD digits, then you only need 3 10x10 look-up tables for multiplication, division, and mod. I don't know if table indexing will be much faster than the ALU, but it might be. – jxh Jul 23 '13 at 16:14