BCD中的位移操作

3

我正在将BCD数字的位数向左或向右移动,以快速实现乘以或除以2。这里是一个快速向左移位的例子:

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;
   }
}

这个函数的效果很好,如果你传入一个数组 {4,5,6} ,它会返回 {9,1,2} 。问题在于,如果我想一次移动超过一个二进制位,就不得不一遍又一遍地调用这个函数。有没有什么聪明的办法可以在不先将BCD数转换为十进制数的情况下一次性移动超过一个二进制位?


1
如果这不是C语言,请在您的问题中[编辑]以放置正确的语言标签。永远不要忘记语言标签,它是最重要的标签。 - Mat
2
arg[i]<<n,其中n是要移位的位数。 - nj-ath
该函数如何处理输入{9,1,2}?需要额外的输出数字(字节)吗?一般来说,对于大于1的移位,您可能需要许多额外的输出值字节。您可以将接口泛化为:size_t BCD_LShift(unsigned char *value, size_t vallen, size_t lshift, unsigned char *result, size_t reslen);其中返回值是结果中BCD数字的数量。我想知道在函数重复调用和“转换为二进制整数,移位,转换为BCD”之间的断点在哪里? - Jonathan Leffler
单个数字 4 << 2 在 BCD 中得出的是 10,这显然是错误的,因为它应该是 (0x)16。只有通过在每个阶段进行调整的逻辑才能实现这一点:4 << 1 = 8,8<<1 = (8 + 3) << 1 = 16。 - Aki Suihkonen
谢谢大家的评论。@AkiSuihkonen,这正是我担心的。看起来可能没有解决办法。 - Joey Shepard
3个回答

3
请看下文,N表示要移位的位数,假设N<=3,如果你移动超过3个位置(N>3),则需要处理多于一位数的进位(例如,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;
   }
}

如果您希望接近原始内容,请选择以下选项:
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;
   }
}

同时需要注意的是,在所有版本(包括原始版本)中,你可能会留下一个进位,这是一位新的数字。


这个不起作用。在BCD中,每个阶段都必须调整大于4的数字(加3)。 - Aki Suihkonen
1
问题不在移位,而在BCD纠正上(即“if (temp>9) temp += 6;”这行代码,它只适用于1位移位)。 - Chris Dodd
对啊,我忘了这个程序依赖于进位永远不超过1的事实,已经修正了。 - Ofir
@Ofir 谢谢。这看起来像我要找的东西。是否没有办法绕过除法?我正在使用没有乘法或除法硬件支持的微控制器,这就是我为什么首先进行移位的原因。话虽如此,即使有了那个除法,它可能比我之前调用函数三次更快。 - Joey Shepard
可以使用查找表来避免除法(我不确定这是否更适合您的目的)。 - Ofir

1
你需要重新设计函数,将要移位的位数作为参数包含在其中。并使用该参数将BCD字节向左移动一定数量的位数。
void LShift(unsigned char *arg) can be modified to void LShift(unsigned char *arg,int n)

并执行

temp=(arg[i]<<n)+carry;

0
编写移位逻辑有点复杂,因为进位操作有点棘手。它变成了一段看起来非常像移位和加法乘法实现的代码。
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));
}

我认为从BCD转换回来进行移位会更简单高效。我以直接的方式实施了它,我相信转换操作可以优化。

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;
    }
}

是的,在这种情况下,可能只需转换为十进制更好。任何形式的乘法或除法都会拖慢代码,因为它们将被转换为重复的加法。我正在使用位移来执行CORDIC例程,因此需要尽可能快地完成它们。不过,似乎必须进行乘法和除法。 - Joey Shepard
如果你真的只有3个BCD数字,那么你只需要3个10x10的查找表来进行乘法、除法和取模运算。我不知道表索引是否比ALU更快,但可能会更快一些。 - jxh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接