在字符串中乘以两个任意数字

时间:2016-11-03 16:47:05

标签: c string math arbitrary-precision

我已经使用以下代码在C:

中执行存储在char *中的2个任意数字之间的乘法运算
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void    mult(char *n1, char *n2)
{
  char  *res;
  int   mul, i, j;

  res = malloc(sizeof(*res) * (strlen(n1) + strlen(n2) + 1));
  memset(res, '0', strlen(n1) + strlen(n2));
  res[strlen(n1) + strlen(n2)] = 0;
  for (i = strlen(n1) - 1; i >= 0; i--)
  {
     for (j = strlen(n2) - 1; j >= 0; j--)
     {
        mul = (n1[i] - '0') * (n2[j] - '0');
        res[i + j] += ((res[i + j + 1] + mul - '0') / 10);
        res[i + j + 1] = ((res[i + j + 1] + mul - '0') % 10) + '0';
      }
    }
  printf("%s\n", res);
  free(res);
}

我使用-O3标志编译它,但对于86 k位的大数字,它需要大约30秒。 我怎样才能让它更快?

4 个答案:

答案 0 :(得分:2)

总结评论:

  • 尽量避免整数除法。例如。查找表。两位数的乘积意味着最多9 ^ 2。使用/ 10或%10的结果作为另一个。
  • 确保存储strlen()调用,为它们显式指定变量。
  • 执行+ / - &#39; 0&#39; trafo分开。
  • 考虑基本10000建议。

也许使用专用struct(dyn数组+维护大小信息)会比string更好。长度和+ / - &#39; 0&#39;只有在需要数字的I / O时才会执行。

答案 1 :(得分:0)

以下是一个稍微优化的版本,使用了很多评论&#39;建议:

void mult(char *n1, char *n2)
{
  char  *res, *pres, *p1, *p2;
  int   mul, i, j, l1= strlen(n1), l2= strlen(n2);

  res = malloc(sizeof(*res) * (l1 + l2 + 1));
  memset(res, 0, l1 + l2);
  res[l1 + l2] = 0;

  for (i=0, p1=n1; i<l1; i++) *p1++ -= '0';
  for (j=0, p2=n2; j<l2; j++) *p2++ -= '0';

  p1= n1+l1-1;
  for (i= l1 - 1; i >= 0; i--, p1--)
  {
     p2= n2+l2-1;
     pres= res+i;
     for (j = l2 - 1; j >= 0; j--, p2--)
     {
        mul = *p1 * *p2;
        pres[j] += ((pres[j + 1] + mul) / 10);
        pres[j + 1] = ((pres[j + 1] + mul) % 10);
      }
    }
  for (i=0, p1=res; i<l1+l2; i++) *p1++ += '0';
  printf("%s\n", res);
  free(res);
}

答案 2 :(得分:0)

  1. 避免重复调用strlen()。在for (j = strlen(n2) ...循环中@Michael Walzfor (i ...

  2. 不是重复减去/添加'0',而是在开头和结尾处执行一次。对于很长的字符串来说这是值得的,对于短字符串则是如此。 @Michael Walz @Eugene Sh.

  3. 不要触摸内循环中的2个产品元素(一次添加到产品中,一次用于携带),请执行一次并形成进位

  4. 利用前导零。

  5. 一些未经测试的代码。一定要释放释放返回的指针。编码为清晰与代码打高尔夫。

    #include <stdlib.h>
    #include <string.h>
    
    static void string_offset(char *s, size_t sz, int offset) {
      while (sz > 0) {
        sz--;
        s[sz] += offset;
      }
    }
    
    char * string_string_mult(char *a, char *b) {
      while (*a == '0') a++;
      size_t alen = strlen(a);
      size_t asz = alen + 1;
      string_offset(a, alen, -'0');
    
      while (*b == '0') b++;
      size_t blen = strlen(b);
      size_t bsz = blen + 1;
      if (a != b) {       // TBD to fully detect a,b, overlap
        string_offset(b, blen, -'0');
      }
    
      size_t psz = asz + bsz;
      char *product = calloc(psz, sizeof *product); // account for potential carry and \0
      if (product == NULL) return NULL;
    
      for (size_t ai = alen; ai > 0;) {
        ai--;
        char *p = product + ai + blen + 1;
        int acc = 0;
        for (size_t bi = blen; bi > 0;) {
          bi--;
          acc = *p + a[ai] * b[bi] + acc;
          *p = acc %= 10;
          p--;
          acc /= 10;
        }
        *p += acc;
      }
      string_offset(product, psz - 1, +'0');
    
      // test for an unneeded leading zero
      if (product[0] == '0' && (alen + blen > 0)) {
        memmove(product, product + 1, psz - 1);
      }
    
      string_offset(a, alen, +'0');
      if (a != b) {       // TBD to fully detect a,b, overlap
        string_offset(b, blen, +'0');
      }
      return product;
    }
    

    详细

    • ab可能指向相同的字符串。
    • 内存分配可能会失败。
    • size_t是用于数组索引的最佳类型。
    • 产品可能/可能不会形成最终的携带物。

答案 3 :(得分:0)

我检查了大多数建议的优化(LutzL&amp; 10000基础10000计划除外)并且它们没有显着差异。由于您已经确定bc拥有正确的内容,为什么不简单地使用它而不是尝试重新实现它:

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

#define BUFFER_SIZE 1024

void mult(const char *n1, const char *n2) {
    FILE *pipe = popen("/usr/bin/bc", "r+");

    fprintf(pipe, "%s * %s\nquit\n", n1, n2);

    if (ferror(pipe)) {
        fprintf(stderr, "Output to pipe failed.\n");
        exit(EXIT_FAILURE);
    }

    char *buffer = malloc(BUFFER_SIZE); // bc long lines are limited to 70 characters
    char *result = fgets(buffer, BUFFER_SIZE, pipe);

    while (result != NULL) {
        char *s = rindex(buffer, '\\'); // find and remove line continuation marker

        if (s != NULL) {
            *s = '\0';
        }

        fputs(buffer, stdout);

        result = fgets(buffer, BUFFER_SIZE, pipe);
    }

    (void) pclose(pipe);
}

在我的系统上,这会在一秒钟内将两个86K的数字相乘。

请注意,我的系统(OSX)具有双向popen(),这在许多系统上是单向的,因此您需要使用建议的解决方案来进行双向通信。

上面的代码可以使用更多错误检查,您可以使用bc环境变量BC_LINE_LENGTH来生成更长的行以提高效率,并且在某些系统上将其设置为零以避免换行。< / p>