【问题标题】:32-bit multiplication through 16-bit shifting通过 16 位移位实现 32 位乘法
【发布时间】:2014-09-08 04:33:20
【问题描述】:

我正在编写一个使用移位和加法的软乘法函数调用。现有的函数调用如下:

unsigned long __mulsi3 (unsigned long a, unsigned long b) {

    unsigned long answer = 0;

    while(b)
    {
        if(b & 1) {
            answer += a;
        };

        a <<= 1;
        b >>= 1;
    }
    return answer;
}

虽然我的硬件没有乘法器,但我有一个硬移位器。移位器一次最多可移位 16 位。

如果我想充分利用我的 16 位移位器。关于如何调整上面的代码以反映我的硬件功能的任何建议?给定的代码每次迭代仅移动 1 位。

16 位移位器一次最多可将 32 位无符号长整数值移位 16 位。 sizeof(unsigned long) == 32 位

【问题讨论】:

  • 那么,在你的机器上,sizeof(unsigned long) == 4 &amp;&amp; CHAR_BIT == 8?值得说明的是,因为我主要在 64 位上工作,所以默认情况下 sizeof(unsigned long) == 8 对我来说,可能还有很多其他人。您的 16 位移位器只能移位 16 位(unsigned short,还是 unsigned int?)数量,而不是 32 位数量?或者它可以一次将 32 位 unsigned long 值最多移动 16 个位置?还是别的什么?
  • 感谢您帮助我更好地改进我的问题。在您指出之前,我从未想过这个问题。
  • 直到您指出这一点,我才意识到我并不准确。后者是正确的:16 位移位器一次最多可以将 32 位无符号长整型值移位 16 位。 sizeof(unsigned long) == 32 位

标签: c assembly bit-manipulation multiplication bit-shift


【解决方案1】:

移动多个位的能力不会有太大帮助,除非你有一个硬件乘法器,比如 8 位 x 8 位,或者你可以负担一些 RAM/ROM 来做(比如)4 位4 位乘以查找。

通过交换参数以使乘数更小,可以帮助进行简单的移位和添加(正如您所做的那样)。

如果您的机器通常更快地执行 16 位操作,那么将您的 32 位 'a' 一次处理为 'a1:a0' 16 位,同样地处理 'b',您可能也可以这样做一些周期。你的结果只有 32 位,所以你不需要做 'a1 * b1' - 尽管其中一个或两个可能为零,所以胜利可能不大!此外,您只需要 'a0 * b1' 的 ls 16 位,因此可以完全使用 16 位来完成 - 但如果 b1(假设 b

跳过乘数零的运行可能会有所帮助 - 取决于处理器和乘数的任何属性。

FWIW:根据我的小经验,做魔术 'a1*b1', '(a1-a0)*(b0-b1)', 'a0*b0' 并通过移位、加法和减法组合结果,一场绝对的噩梦……'(a1-a0)'、'(b0-b1)' 和他们的产品的标志必须得到尊重,这使得看起来像一个可爱的把戏有点混乱。当你完成加减运算时,你必须有一个非常缓慢的乘法才能让这一切都值得!当乘以非常非常长的整数时,这可能会有所帮助......但内存问题可能会占主导地位......当我尝试它时,它有点令人失望。

【讨论】:

    【解决方案2】:

    具有 16 位移位可以帮助您使用以下方法进行轻微的速度提升:

    (U1 * P + U0) * (V1 * P + V0) = = U1 * V1 * P * P + U1 * V0 * P + U0 * V1 * P + U0 * V0 = = U1 * V1 * (P*P+P) + (U1-U0) * (V0-V1) * P + U0 * V0 * (1-P)

    假设 P 是 2 的方便幂(例如,2^16、2^32),因此乘以它是一个快速移位。这将较小数字的乘法从 4 次减少到 3 次,并且对于非常长的数字,递归地用 O(N^1.58) 而不是 O(N^2)。

    这个方法被命名为Karatsubaʼs multiplication。那里描述了更高级的版本。

    对于小数字(例如 8 x 8 位),如果您有足够快的 ROM,则以下方法很快:

    a * b = 平方(a+b)/4 - 平方(a-b)/4

    如果要对int(square(x)/4) 进行制表,无符号乘法需要 1022 个字节,有符号乘法需要 510 个字节。

    【讨论】:

    • OP 想要截断 C 乘法(32x32 => 32 位),而不是完全乘法(32x32 => 64 位)。所以我们可以去掉 P=2^16 的 P*P 术语。我认为 user3793679 是正确的,假设对 32 位值进行操作是有效的,这里可能没有任何好处。除非您可能像建议的那样将 LUT 用于小型乘法,否则可能。
    【解决方案3】:

    基本方法是(假设移位 1):-

    • 移动前 16 位
    • 将高16位的低位设置为低16位的高位
    • 移动低 16 位

    取决于您的硬件...

    但你可以试试:-

    • 假设 unsigned long 为 32 位
    • 假设大端

    然后:-

     union Data32
            {
               unsigned long l;
               unsigned short s[2];
            }; 
    
    unsigned long shiftleft32(unsigned long valueToShift, unsigned short bitsToShift)
    {
        union Data32 u;
        u.l  = valueToShift
        u.s[0] <<= bitsToShift;
        u.s[0] |= (u.s[1] >> (16 - bitsToShift);
        u.s[1] <<= bitsToShift
    
        return u.l;
    }
    

    然后反过来做同样的事情来右移

    【讨论】:

      【解决方案4】:

      上面的代码是在传统的方式上乘以我们在小学学习的方式:

      前:

          0101
        * 0111
        -------
          0101
         0101.
        0101..
       --------
        100011
      

      当然,如果您没有乘法器或 1 位移位器,则不能这样处理! 不过,您可以通过其他方式进行操作,例如循环:

      unsigned long _mult(unsigned long a, unsigned long b)
      {
          unsigned long res =0;
      
          while (a > 0)
          {
              res += b;
              a--;
          }
      
          return res;
      } 
      

      它很昂贵,但可以满足您的需求,无论如何,如果您有更多限制(例如计算时间......),您可以考虑其他方法

      【讨论】:

      • OP 有一个移位器,一次操作可以将 32 位数字移位最多 16 位。他们想知道是否可以通过使用更大的移位来改进他们现有的一次仅移位 1 位的乘法例程。
      猜你喜欢
      • 1970-01-01
      • 2017-03-11
      • 1970-01-01
      • 2015-04-12
      • 2011-12-04
      • 1970-01-01
      • 2013-09-18
      • 1970-01-01
      • 2011-08-14
      相关资源
      最近更新 更多