IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> C语言丨小 学 数 学(二):高精度乘法 -> 正文阅读

[数据结构与算法]C语言丨小 学 数 学(二):高精度乘法

计算机,顾名思义,是用于高速计算的电子计算机器。它可以进行数值计算,又可以进行逻辑计算,还具有存储记忆功能。是能够按照程序运行,自动、高速处理海量数据的现代化智能电子设备。[1]但是,计算机的功能毕竟是有限的,再强大的计算机在面对一些问题时也有它的局限性,本系列文章就来具体阐述其中的一种问题:高精度运算。

先从一个问题说起:编程计算并输出1~40之间的所有数的阶乘。


乍一看题目,似乎很简单:在初学循环语句的时候,累乘问题是一个较基本的问题,实现其功能的代码如下:

#include <stdio.h>
int main(void)
{
    int i;
    long p = 1;
    for (i = 1; i <= 40; i++)
    {
        p = p * i;
        printf("%d! = %ld\n",i,p);//输出1~40之间的所有数的阶乘值
    }
    return 0;
}

运行一下程序,我们就会发现情况和我们想象的不太一样:

啊这……似乎从十几的阶乘开始,就出现了一些不可描述的错误。为什么会这样呢?

其实这正是因为long类型变量有他存储范围的上限,当超过这个范围时,数值便会溢出,导致发生错误。那么有没有方法可以解决这个问题呢?我们就需要用到数组:用一个包含50个元素的数组存储一个大数,每个数组元素存储大数中的一位数字

而两个数据的乘法则需要模拟乘法竖式的运算,忘记了竖式运算方法的朋友们可以自行翻阅《人民教育出版社数学四年级上册》第四章:三位数乘两位数[2]哦~(不会吧不会吧不会真的有人不如小学生吧(狗头))

先来复习一下:乘法的竖式运算分为三步:(1)位数多的数的每一位和位数少的数的个位相乘,位数多的数的每一位和位数少的数的十位相乘,……,位数多的数的每一位和位数少的数的最高位相乘;(2)按由乘低位到乘高位的以此往前一位写每一次乘得的结果;(3)所有结果相加,得到最终结果。其中,后位向前进位的步骤蕴含在第一步和第三步中。

乍一听是不是很复杂?其实原理大家都懂,只是描述的方式不同。但其实在乘法的竖式运算的步骤是经过人为把乘法运算简化的,其中第二步就是一个简化的结果,涉及到一些运算技巧。那我们如果要让计算机实现第二步,就显得比较困难。如果位数少的数只有一两位还好办,但如果位数少的数的位数也比较多,这时第二步的处理就会显得尤为复杂。

这时候,我们就要回到多位数乘法的本质:多位数乘法实质上是一个多位数的逐位与另一个多位数相乘,并使后位向前进位,最终得到一个新的多位数。而进位的原则就是让每一位的数都处于0~9之间

我们先把问题拆解:首先我们要有一个存储大数的数组,于是我们定义一个常量SIZE为51,并定义数组int data[SIZE] = {0};。之所以要定义一个长度为51的数组,是为了在后续判断大数位数是否大于50位,若大于50,数组溢出,则提示错误信息,这就需要另外定义一个变量int index = 1;记录数组元素个数,表示阶乘值的位数;而把数组初始化为0,是为了避免数组未初始化而产生随机值导致错误。而此后应让data[1] = 1;,准备计算阶乘。

接下来我们要定义一个变量int n;,n的值由用户输入,含义为准备计算的阶乘中的最大数。为避免用户输入非法数据,我们需要检测用户输入数据的合法性:

printf("Input n:");
int ret = scanf("%d", &n);
char c = getchar();
while ((ret!=1)||(n<=0)||(c!='\n'))
{
    printf("Input int n > 0:");
    fflush(stdin);
    ret = scanf("%d", &n);
    c = getchar();
}

具体原理可参见我之前的文章C语言丨检测用户键盘输入数据的合法性,这里不再赘述。

得到了准备计算的阶乘中的最大数后,我们就可以开始使用循环,计算1~n之间所有数的阶乘值了:

for (i=1; i<=n; i++)//计算阶乘i
{
    for (j=1; j<=index; j++)
    {
        data[j] = data[j] * i;//每一位数字都乘以i
    }
}

让第二重循环的执行条件为j<=index而非j<SIZE的原因是index记录的是数组元素的个数,也即该储存的大数的位数,当j>index时,data[j]全部为0,而0乘任何数都为0,就没必要进行多余的计算。

那么使用了这样一个循环条件,必然要在每一次计算中改变index的值。根据我们为index赋值的含义,只要数组中的最高位元素大于等于10,即data[index] >= 10,则意味着该数的位数要大于index,此时则需要执行index++;操作。通过如下代码可实现:

//单独处理最高位,若计算之后的最高位大于或等于10,则位数index加1
while (data[index] >= 10 && index <= SIZE-1)
{
    data[index+1] = data[index] / 10;//向最高位进位
    data[index] = data[index] % 10;//进位之后的值
    index++;//位数index加1
}

其实在处理最高位时,我们已经用到了进位的方法,也即while循环中的前两行,这是进位的核心代码。把index换为k,让k遍历1~index-1的所有值,即可将除最高位之前所有位进位。此段代码应放在处理最高位的前面,因为只有先处理了最高位之前的所有位,才有所谓的“最高位”

因此我们可以整理得计算阶乘的代码如下:

for (i=1; i<=n; i++)//计算阶乘i
{
    for (j=1; j<=index; j++)
    {
        data[j] = data[j] * i;//每一位数字都乘以i
    }
    for (k=1; k<index; k++)
    {
        if (data[k] >= 10)//若阶乘值的每位数字大于或等于10,则进位
        {
            data[k+1] = data[k+1] + data[k]/10;//当前位向前进位
            data[k] = data[k] % 10;//进位之后的值
        }
    //单独处理最高位,若计算之后的最高位大于或等于10,则位数index加1
    while (data[index] >= 10 && index <= SIZE-1)
    {
        data[index+1] = data[index] / 10;//向最高位进位
        data[index] = data[index] % 10;//进位之后的值
        index++;//位数index加1
    }
}

到此为止,我们已经完成了阶乘计算的操作了,剩下要做的的只有打印阶乘值。这时我们之前定义的SIZE=51以及index就起作用了:若index>50,则表示数组溢出,提示错误信息。

if (index <= SIZE-1)//检验数组是否溢出,若未溢出,则打印阶乘值
{
    printf("%d! = ", i);
    for (j=index; j>0; j--)//从最高位开始打印每一位阶乘值
    {
        printf("%d", data[j]);
    }
    printf("\n");
}
else//若大于50,数组溢出,则提示错误信息
{
    printf("Overflow! \n");
    exit(1);
}

此处,index的作用再一次体现了出来:我们无需处理最高位前的所有0元素,只需从最高位开始打印阶乘值的每一位数即可

到此,我们就完成了整个题目的代码编写了。让我们来回顾一下整个历程:逐位相乘->后位向前进位->处理最高位->输出阶乘值。下面给出完整的代码:

#include <stdio.h>
#include <stdlib.h>
#define SIZE 51
int main(void)
{
    int data[SIZE]={0};//存储50位数,元素全部初始化为0,不使用data[0]
    int index = 1;//数组元素个数,表示阶乘值的位数
    int n;//准备计算的阶乘中的最大数
    int i, j, k;
    data[1] = 1;//初始化,令1!=1
    printf("Input n:");
    int ret = scanf("%d", &n);
    char c = getchar();
    while ((ret!=1)||(n<=0)||(c!='\n'))
    {
        printf("Input int n > 0:");
        fflush(stdin);
        ret = scanf("%d", &n);
        c = getchar();
    }
    for (i=1; i<=n; i++)//计算阶乘i
    {
        for (j=1; j<=index; j++)
        {
            data[j] = data[j] * i;//每一位数字都乘以i
        }
        for (k=1; k<index; k++)
        {
            if (data[k] >= 10)//若阶乘值的每位数字大于或等于10,则进位
            {
                data[k+1] = data[k+1] + data[k]/10;//当前位向前进位
                data[k] = data[k] % 10;//进位之后的值
            }
        //单独处理最高位,若计算之后的最高位大于或等于10,则位数index加1
        while (data[index] >= 10 && index <= SIZE-1)
        {
            data[index+1] = data[index] / 10;//向最高位进位
            data[index] = data[index] % 10;//进位之后的值
            index++;//位数index加1
        }
        if (index <= SIZE-1)//检验数组是否溢出,若未溢出,则打印阶乘值
        {
            printf("%d! = ", i);
            for (j=index; j>0; j--)//从最高位开始打印每一位阶乘值
            {
                printf("%d", data[j]);
            }
            printf("\n");
        }
        else//若大于50,数组溢出,则提示错误信息
        {
            printf("Overflow! \n");
            exit(1);
        }
    }
    return 0;
}

[3]


和高精度加法一样,代码的核心就是人工模拟计算的过程,其中包括加/乘法和进位,只要能解决这两个问题,一切高精度算法都可以迎刃而解。


参考文献:

[1]百度百科.

[2]数学四年级上册,人民教育出版社,P48-56.

[3]苏小红 王甜甜 赵玲玲 范江波 车万翔 等编著 王宇颖 主审,C语言程序设计学习指导(第4版),高等教育出版社,P126-127.

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-12-07 12:17:52  更:2021-12-07 12:18:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 14:48:42-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码