问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

C语言:阶乘的高精度计算

创作时间:
作者:
@小白创作中心

C语言:阶乘的高精度计算

引用
CSDN
1.
https://blog.csdn.net/Wu_Wei6/article/details/133049691

在编程中,计算一个正整数的阶乘是一个常见的问题。然而,当这个正整数足够大时,普通的整型数据类型就无法储存如此大的结果。本文将介绍如何使用高精度计算算法来解决这一问题,重点讲解了高精度乘法的原理和实现方法,并给出了基于高精度乘法实现阶乘计算的具体代码。

前言

计算一个正整数的阶乘在思路上并不难,难的是当这个正整数足够大时,编程语言的整型数据类型便无法储存如此大的结果。这篇文章将介绍如何用高精度计算算法解决这一问题。

高精度阶乘问题

题目描述

参考题目连接:PTA | 程序设计类实验辅助教学平台 (pintia.cn)

题目要求很简单:输入一个整数,输出它的阶乘。

低精度算法及局限性

按比较直接的思路,用一个for循环即可解决,代码如下

void Print_Factorial ( const int N ){
    int s = 1;
    for(int i=1;i<=N;i++){
        s *= i;
    }
    printf("%d",s);
}

或者是用一个简单的递归函数:

int Factorial(int N){
    if(N == 1) return 1;
    return N*Factorial(N-1);
}
void Print_Factorial ( const int N ){
    printf("%d",Factorial(N));
}

以上都是一些有一定可行性的做法。但是题目对N的要求为“其值不超过1000”,也就是说,N的值能够做到足够大使得结果无法被C中所有整型数据结构储存。如

100!=93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

这个时候,以上方法便不适用了。

高精度算法

原理

高精度算法,顾名思义,便是比简单数字运算精度更高的一种算法。这种精度就体现在可以进行大数值的运算。

首先要解决的问题便是:怎么才能够储存一个巨大的数值呢?答案是用一个数组储存每一位上的数字。这样一来,我们定义一个长度100一维数组,就可以存放一个位数小于等于100的整数,这已经远远超出了long类型所能存放的大小。

解决了数据存储的问题,另一个问题便随之而来:把数据存储在数组里,该怎么实现两个变量之间的运算呢?其实,不论是高精度加法,减法,还是乘法,其计算模式都与一种小学生都会的方法十分相似——列竖式

下面以高精度乘法为例。

高精度乘法

对两个数进行高精度乘法的过程就跟对他们进行列竖式计算差不多:

列竖式时,我们将其中一个数A作为“被乘数”,另一个数B作为“乘数”,先用B的第1位(个位)与A的各个位相乘,得到一个结果n1;再用B的第2位(十位)与A的各个位相乘,得到第二个结果n2......重复这个过程直到B的最后一位,将各次结果乘上相印系数后相加即可。

而在C中,用数组实现这一过程更简单,省去了一些不必要的步骤。

下面讨论如何用程序实现。

首先便是要输入两个待计算的数字。由于待乘数可能会很大,故在输入时,直接先将它们存入字符形式数组,再转化位int类型数组(同时进行倒置)。过程函数如下:

int PutInNum(char a[]){
    int i=0;
    char c;
    while(scanf("%c", &c) && c != '\n'){    //遇到换行(回车)时停止输入
        a[i++] = c;
    }
    return i;   //返回数字的长度
}
/*转换数组类型并倒置*/
void Transform(char n1[], int n2[], int l){     //此处int类型的数组n2应初始化为{0}
    int p = l-1;    
    for(int i=0;i<l;i++){
        n2[p--] = (int)n1[i] - 48;    //‘1’的ascii为49,故减去48便得到数字1
    }
}

此处进行倒置是为了后面计算方便。此处不展开,后文再细讲。

将数字存入数组后,便是计算部分。我们创建一个数组res来储存结果。res同样应初始化为{0},为计算答案做准备。

计算思路就是以n1的每一位与n2的每一位做乘法后逐次将结果存入res数组。在数十种,我们知道,十位的数乘以百位的数,结果的最低位数便是10*100 = 1000即千位(2+3-1=4即第四位)。那么在进行高精度乘法时也是一样,n1的第2位n1[1],与n2的第3位n2[2]相乘,所得位数就应该为2+3-1=4,即res[3]。我们惊奇的发现,其实最后存入res的位置,就是n1,n2对应位数的索引值之和(1+2=3)。

由此可以得出,n1[i]与n2[j]之积的初始位数在res[i+j].(这就是倒置的优势,而且还可以从0开始遍历)

而n1[i]与n2[j]之积可能是一个两位数,我们需要将它们的积的十位部分加入res[i+j+1],即**res[i+j+1] = (n1[i]n2[j])/10,res[i+j] = (n1[i]n2[j])%10

这样计算后得到的数组,再从后往前遍历输出,便可得到最终答案。计算函数如下

int Print_MULT(int n1[], int n2[], int l1, int l2){
    int res[MAXN] = {0};    //储存结果的数组
    int len = 0;
    /*n2做初始数,n1做乘上的数
    即在乘法竖式中,n2在上,n1在下*/
    for(int i=0;i<l1;i++){  //i指向n1当前运算位数
        for(int j=0;j<l2;j++){  //j指向n2当前运算位数
            int tmp = n1[i] * n2[j];
            res[i+j] += tmp;
            if(res[i+j] >=10){
                res[i+j+1] += (int)res[i+j]/10;
                res[i+j] %= 10;
                len = (i+j+1 > len)?i+j+1:len;
            }
            else len = i+j;
        }
    }
    for(int i=len; i>=0;i--) printf("%d",res[i]);
}

完整代码如下:

#include<stdio.h>
#define MAXN 10000
/*输入数字*/
int PutInNum(char a[]){
    int i=0;
    char c;
    while(scanf("%c", &c) && c != '\n'){    //遇到换行(回车)时停止输入
        a[i++] = c;
    }
    return i;   //返回数字的长度
}
/*转换数组类型并倒置*/
void Transform(char n1[], int n2[], int l){     //此处int类型的数组n2应初始化为{0}
    int p = l-1;    
    for(int i=0;i<l;i++){
        n2[p--] = (int)n1[i] - 48;    //‘1’的ascii为49,故减去48便得到数字1
    }
}
int Print_MULT(int n1[], int n2[], int l1, int l2){
    int res[MAXN] = {0};    //储存结果的数组
    int len = 0;
    /*n2做初始数,n1做乘上的数
    即在乘法竖式中,n2在上,n1在下*/
    for(int i=0;i<l1;i++){  //i指向n1当前运算位数
        for(int j=0;j<l2;j++){  //j指向n2当前运算位数
            int tmp = n1[i] * n2[j];
            res[i+j] += tmp;
            if(res[i+j] >=10){
                res[i+j+1] += (int)res[i+j]/10;
                res[i+j] %= 10;
                len = (i+j+1 > len)?i+j+1:len;
            }
            else len = i+j;
        }
    }
    for(int i=len; i>=0;i--) printf("%d",res[i]);
}
int main(){
    char a[MAXN], b[MAXN];
    int la = PutInNum(a); int lb = PutInNum(b);
    int n1[MAXN] = {0}; int n2[MAXN] = {0};
    Transform(a ,n1,la); Transform(b, n2, lb);
    Print_MULT(n1,n2,la,lb);
    return 0;
}

高精度算法实现阶乘计算

接下来进入正题:如何基于高精度乘法进行阶乘计算?

PTA | 程序设计类实验辅助教学平台 (pintia.cn)

这其实就是一个进行若干次高精度乘法的过程。求n的阶乘,只需先将n与n-1相乘,其结果再与n-2相乘...如此往复,便可得到结果。

计算n!的过程,仍然是用到n1,n2,res这三个数组。第一次相乘,n1为n,n2为n-1,结果存入res中。再将n1初始化为res,res重置为{0},n2 = n2 - 1,再进行一次乘法,结果存入res中......直到n2=1时,res中的数便是结果。

实现代码如下:

#include <stdio.h>
void Print_Factorial(const int N);
int main()
{
    int N;
    scanf("%d", &N);
    Print_Factorial(N);
    return 0;
}
#define MAXN 10000
int Print_MULT(int n1[], int n2[], int l1, int l2, int res[], int len)
{
    /*n2做初始数,n1做乘上的数
    即在乘法竖式中,n2在上,n1在下*/
    for (int k = 0; k <= l1; k++)
    { // i指向n1当前运算位数
        for (int j = 0; j < l2; j++)
        { // j指向n2当前运算位数
            int tmp = n1[k] * n2[j];
            res[k + j] += tmp;
            if (res[k + j] >= 10)
            {
                res[k + j + 1] += (int)res[k + j] / 10;
                res[k + j] %= 10;
                len = (k + j + 1 > len) ? k + j + 1 : len;
            }
            else
                len = k + j;
        }
    }
    return len;
    // for(int i=len; i>=0;i--) printf("%d",res[i]);
}
void Print_Factorial(const int N)
{
    if (N == 0)
        printf("1");
    else if (N < 0)
        printf("Invalid input");
    else
    {
        int n1[MAXN] = {0};
        int time = 0;
        int last_r = 0;
        int p = 0;
        for (int i = 10; time == 0; i *= 10)
        {
            if (N % i == N)
                time++;
            int r1 = N % i;
            int r2 = r1 - last_r;
            last_r = r1;
            n1[p++] = r2 / (i / 10);
        }
        int flag = 0;
        int n2[MAXN] = {0};
        int len = p - 1;
        for (int i = 0; i < p; i++)
        {
            n2[i] = n1[i];
        }
        for (int i = 0; i < N - 1; i++)
        {
            int res[MAXN] = {0};
            n2[0] -= 1;
            for (int j = 0; j < p; j++)
            {
                if (n2[j] < 0)
                {
                    n2[j] = 9;
                    n2[j + 1] -= 1;
                }
                if (n2[p - 1] == 0)
                    p--;
            }
            len = Print_MULT(n1, n2, len, p, res, len);
            for (int j = 0; j <= len; j++)
            {
                n1[j] = res[j];
            }
        }
        for (int i = len; i >= 0; i--)
            printf("%d", n1[i]);
    }
}
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号