使用字符串实现非常大的数字相乘

6
我将尝试编写一个C程序,实现两个数字的乘法而不直接使用乘法运算符,并且它应该考虑到数字足够大,以至于即使这两个数字的常规加法也无法通过直接相加来执行。
当我尝试(并成功地)编写一个使用字符字符串执行加法的C程序时,我受到了启发,我做了以下操作:
#include<stdio.h>
#define N 100000
#include<string.h>
void pushelts(char X[], int n){
int i, j;
for (j = 0; j < n; j++){
    for (i = strlen(X); i >= 0; i--){
        X[i + 1] = X[i];
    }
        X[0] = '0'; 
    }
}

int max(int a, int b){
    if (a > b){ return a; }
    return b;
}

void main(){
    char E[N], F[N]; int C[N]; int i, j, a, b, c, d = 0, e;
    printf("Enter the first number: ");
    gets_s(E);
    printf("\nEnter the second number: ");
    gets_s(F);
    a = strlen(E); b = strlen(F); c = max(a, b);
    pushelts(E, c - a); pushelts(F, c - b);
    for (i = c - 1; i >= 0; i--){
        e = d + E[i] + F[i] - 2*'0';
        C[i] = e % 10; d = e / 10;
    }
    printf("\nThe answer is: ");
    for (i = 0; i < c; i++){
        printf("%d", C[i]);
    }
    getchar();
}

它可以对“N”位数的任意两个数字进行相加。现在,我该如何使用它来执行大数的乘法运算呢?首先,我编写了一个函数,用于将要作为字符字符串输入的数字乘以一个数字n(即0<=n<=9)。很容易看出这样的函数是如何编写的;我将其称为(*)。现在主要目的是将两个数字(按字符字符串输入)相乘。我们可以将第二个数字看作具有k位数字的数字(假设它是a1a2.....ak):

a1a2...ak = a1 x 10^(k - 1) + a2 x 10^(k - 2) + ... + ak-1 x 10 + ak

因此,可以使用为加法设计的解决方案以及功能 (*) 来实现两个数字的乘法。

如果第一个数字是 x1x2.....xn,第二个数字是 y1y2....yk,则:

x1x2...xn x y1y2...yk = (x1x2...xn) x y1 x 10^(k-1) + .....

现在,(*) 函数可以将 (x1x2...xn) 乘以 y1,且乘以10^(k-1)就是在数字旁边添加 k-1 个零;最后,我们将这些 k 个项相加,得到结果。但难点在于只有知道每个数字包含多少位数,才能在设计用于将它们相加的循环中每次执行加法。我考虑过使用空数组,并每次给它添加由 (x1x2....xn) 与 yi x 10^(i-1) 相乘得到的结果,但正如我所说,我无法确定所需的上下界限,也不知道每次要在获取的结果前面添加多少个零,以便使用上述算法将其添加到空数组中。 当需要进行从 char 类型到 int 类型的多次转换时,更加困难。也许我把它变得比应该更复杂了,我不知道是否有更简单的方法可以完成此任务,或者是否有我不知道的工具。 我是编程的初学者,不知道除基本工具外还有什么东西。

是否有任何解决方案、想法或算法可以提供?谢谢。


1
为什么要使用字符串而不是像整数数组这样的东西? - Rafael Lerm
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - user4464734
换句话说,除非你有充分的理由偏爱十进制,否则最好使用基于2^16或2^32(如果你的C编译器可以访问64x64位乘法的128位结果,则使用2^64)进行计算。此外,表示十进制数字的字符串浪费了它们所占空间的246/256(96%)。 - Pascal Cuoq
难道不能使用 BigInt 包来完成这个吗? - Lehs
这是基于NTT的Schönhage-Strassen乘法,附带字符串乘法示例:https://dev59.com/Wuo6XIcBkEYKwwoYLhXh - Spektre
3个回答

6

我在 SPOJ 完成 Small Factorials 题目时,开发了一个算法来解决这个问题。

这个算法基于小学的乘法方法。在学校里,我们学习通过将第一个数字的每个数字与第二个数字的最后一位相乘来计算两个数字的乘积。然后将第一个数字的每个数字与第二个数字的倒数第二个数字相乘,以此类推,如下所示:

               1234
               x 56
         ------------
               7404
             +6170-   // - is denoting the left shift    
         ------------
              69104  

实际上正在发生的事情是:
1. num1 = 1234,num2 = 56,left_shift = 0; 2. char_array[] = num1中的所有数字 3. result_array[] 4. while(num2) 1. n = num2%10 2. num2 /= 10 3. carry = 0, i = left_shift, j = 0 4. while(char_array[j]) i. partial_result = char_array[j]*n + carry ii. partial_result += result_array[i] iii. result_array[i++] = partial_result%10 iv. carry = partial_result/10 5. left_shift++ 5. 以相反的顺序打印result_array。
请注意,如果num1和num2不超过其数据类型的范围,则上述算法有效。如果您想要更通用的程序,则必须在char数组中读取两个数字。逻辑将是相同的。将num1和num2声明为char数组。请参阅实现:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    char num1[200], num2[200];
    char result_arr[400] = {'\0'};
    int left_shift = 0;

    fgets(num1, 200, stdin);
    fgets(num2, 200, stdin);

    size_t n1 = strlen(num1);
    size_t n2 = strlen(num2);   

    for(size_t i = n2-2; i >= 0; i--)
    {
        int carry = 0, k = left_shift;
        for(size_t j = n1-2; j >= 0; j--)
        {
            int partial_result = (num1[j] - '0')*(num2[i] - '0') + carry;
            if(result_arr[k])
                partial_result += result_arr[k] - '0';
            result_arr[k++] = partial_result%10 + '0';
            carry = partial_result/10;  
        }
        if(carry > 0)
            result_arr[k] = carry +'0'; 
        left_shift++;
    }
    //printf("%s\n", result_arr);

    size_t len = strlen(result_arr);
    for(size_t i = len-1; i >= 0; i-- )
        printf("%c", result_arr[i]);
    printf("\n");   
}

这不是一个标准算法,但我希望这可以帮助到您。

1
大数 算法难以高效实现。这些算法相当难以理解(而且高效的算法比你试图实现的朴素算法更好),你可以找到几本相关书籍。
我建议使用现有的 GMPLib 或使用一些提供原生大数支持的语言(例如带有 SBCL 的 Common Lisp)。

0

您可以将字符串相加的代码作为以下方式进行重用(使用user300234的384 x 56示例):

Set result="0" /* using your character-string representation */
repeat:
    Set N = ones_digit_of_multiplier /* 6 in this case */
    for (i = 0; i < N; ++i)
      result += multiplicand  /* using your addition algorithm */
    Append "0" to multiplicand /* multiply it by 10 --> 3840 */
    Chop off the bottom digit of multiplier /* divide it by 10 --> 5 */
    Repeat if multiplier != 0.

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