【问题标题】:In-place integer multiplication就地整数乘法
【发布时间】:2011-12-06 22:59:44
【问题描述】:

我正在编写一个程序(用 C 语言),在该程序中我尝试在尽可能短的时间内计算大数的幂。我将数字表示为数字向量,因此所有操作都必须手写。

如果没有中间结果的所有分配和释放,程序会快得多。是否有任何算法可以进行整数乘法,就地?比如函数

void BigInt_Times(BigInt *a, const BigInt *b);

ab 相乘的结果放在a 内,不使用中间值

【问题讨论】:

  • 那么问题是什么?到目前为止,您尝试过什么?
  • @Macmade:有没有就地进行整数乘法的算法?
  • @Macmade 问题是这样的:“有没有就地进行整数乘法的算法?”;到目前为止,我已经编写了一个进行整数乘法的函数,但不是就地;和一个计算幂的函数(具有log(f) 复杂度,其中f 是乘法函数的复杂度)。
  • 您是否有理由编写此代码,而不是使用已经为 C 编写的许多现有的、高性能、经过测试、记录、正在运行的 bignum 库之一?
  • 这种问题很容易回答,但是我需要一块白板才能这样做......跨度>

标签: c performance algorithm


【解决方案1】:

这里,muln()2n(实际上是 n)n = 2n 无符号整数的就地乘法。您可以调整它以使用 32 位或 64 位“数字”而不是 8 位进行操作。为清楚起见,保留了模运算符。

muln2()n 乘以 n = n 就地乘法(如 here 所示),同样适用于 8 位“数字”。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>

typedef unsigned char uint8;
typedef unsigned short uint16;
#if UINT_MAX >= 0xFFFFFFFF
typedef unsigned uint32;
#else
typedef unsigned long uint32;
#endif
typedef unsigned uint;

void muln(uint8* dst/* n bytes + n extra bytes for product */,
          const uint8* src/* n bytes */,
          uint n)
{
  uint c1, c2;

  memset(dst + n, 0, n);

  for (c1 = 0; c1 < n; c1++)
  {
    uint8 carry = 0;

    for (c2 = 0; c2 < n; c2++)
    {
      uint16 p = dst[c1] * src[c2] + carry + dst[(c1 + n + c2) % (2 * n)];
      dst[(c1 + n + c2) % (2 * n)] = (uint8)(p & 0xFF);
      carry = (uint8)(p >> 8);
    }

    dst[c1] = carry;
  }

  for (c1 = 0; c1 < n; c1++)
  {
    uint8 t = dst[c1];
    dst[c1] = dst[n + c1];
    dst[n + c1] = t;
  }
}

void muln2(uint8* dst/* n bytes */,
           const uint8* src/* n bytes */,
           uint n)
{
  uint c1, c2;

  if (n >= 0xFFFF) abort();

  for (c1 = n - 1; c1 != ~0u; c1--)
  {
    uint16 s = 0;
    uint32 p = 0; // p must be able to store ceil(log2(n))+2*8 bits

    for (c2 = c1; c2 != ~0u; c2--)
    {
      p += dst[c2] * src[c1 - c2];
    }

    dst[c1] = (uint8)(p & 0xFF);

    for (c2 = c1 + 1; c2 < n; c2++)
    {
      p >>= 8;
      s += dst[c2] + (uint8)(p & 0xFF);
      dst[c2] = (uint8)(s & 0xFF);
      s >>= 8;
    }
  }
}

int main(void)
{
  uint8 a[4] = { 0xFF, 0xFF, 0x00, 0x00 };
  uint8 b[2] = { 0xFF, 0xFF };

  printf("0x%02X%02X * 0x%02X%02X = ", a[1], a[0], b[1], b[0]);
  muln(a, b, 2);
  printf("0x%02X%02X%02X%02X\n", a[3], a[2], a[1], a[0]);

  a[0] = -2; a[1] = -1;
  b[0] = -3; b[1] = -1;
  printf("0x%02X%02X * 0x%02X%02X = ", a[1], a[0], b[1], b[0]);
  muln2(a, b, 2);
  printf("0x%02X%02X\n", a[1], a[0]);

  return 0;
}

输出:

0xFFFF * 0xFFFF = 0xFFFE0001
0xFFFE * 0xFFFD = 0x0006

我认为这是我们可以就地做的最好的事情。我不喜欢muln2() 的一件事是它必须积累更大的中间产品,然后传播更大的进位。

【讨论】:

    【解决方案2】:

    好吧,标准算法包括将“a”的每个数字(单词)与“b”的每个数字相乘,并将它们相加到结果中的适当位置。因此,a 的第 i 个数字进入结果的从 i 到 i+n 的每个数字。因此,为了“就地”执行此操作,您需要从最高有效位到最低位计算输出数字。这比从最少到最多做有点棘手,但并不多......

    【讨论】:

      【解决方案3】:

      听起来您并不真的需要算法。相反,您需要更好地使用该语言的功能。

      为什么不直接创建您在答案中指出的功能?使用它并享受! (该函数最终可能会返回对 a 的引用作为其结果。)

      【讨论】:

        【解决方案4】:

        通常,big-int 表示的长度取决于表示的值;一般来说,结果会比任何一个操作数都长。特别是对于乘法,结果表示的大小大致是参数大小的总和。

        如果您确定内存管理确实是您特定平台的瓶颈,您可以考虑实现一个更新第三个值的乘法函数。就您上面的 C 风格函数原型而言:

        void BigInt_Times_Update(const BigInt* a, const BigInt* b, BigInt* target);
        

        这样,您可以像 C++ std::vector 容器一样处理内存管理:您的更新目标只需要在现有大小太小时重新分配其堆数据。

        【讨论】:

        • 我已经有类似你建议的函数(除了它返回值)。另外,我有一个限制,只能使用 C 功能。
        • 我并不是要使用 std::vector 。我的意思是,如果你有更新你的 BigInt 的函数(而不是返回一个新的),你可以保留堆分配的内存而不是重新分配它,只要你不需要更多空间。这就是 std::vector 进行动态分配的方式;我建议你效仿它的例子......
        • 我已经编辑了我的答案;请看一下,看看是否更清楚。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-02-18
        • 1970-01-01
        • 2023-03-29
        • 2015-11-28
        • 2015-03-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多