C++处理大数问题/高精度问题,看这就够了

写在前面:这里是小王成长日志,一名在校大学生,想在学习之余将自己的学习笔记分享出来,记录自己的成长轨迹,帮助可能需要的人。欢迎关注与留言。

0.导入

问题背景

在做算法题时,我们时常会遇到正常的类型进行不了的运算,如两个long long int 相乘并对某个数取模,有时我们可以使用快速幂和龟速乘解决。
但有的题目直接给的是n位的整数,比如下面这道题-洛谷传送门,给出的数据最大已经达到了10000多位,直接使用int,long long 之类的数据必爆无疑。
因此是时候祭出我们的大数处理方法了,大数,就是指利用已有的类型无法进行保存和运算的数字。
其实大数处理,就是利用字符串一位一位保存我们的大数,并且模拟正常的加减乘除过程进行大数的运算。
接下来,我们将一一讨论大数的加减乘除,其中加减乘较简单,除法相较而言难于理解,保持耐心,我默认你已经理解字符串和字符数组的概念,我们开始吧。

题目描述
高精度减法。
输入格式
两个整数 a,ba,b(第二个可能比第一个大)。
输出格式
结果(是负数要输出负号)。
输入输出样例
输入
2
1
输出
1
说明/提示
20 20 20% 数据 a , b a,b a,b 在 long long 范围内;
100 100 100% 数据 0 < a , b ≤ 1 0 10086 0<a,b≤10^{10086} 0<a,b1010086

大数存储

想要对大数进行运算,第一个遇到的如何存储大数,对于这个问题,一般我们采用的都是数组,即利用一个整形数组存储一个数字,如下:
在这里插入图片描述
首先我们定义一个整形数组,再将这个大数一位位的存入这个数组中。
注意:

  1. 一般我们都是翻转保存的,即低位在前,高位在后(个位存在a[0]或a[1]处,这是为了之后模拟运算的时候方便)。
  2. 有的时候我们可以将数组索引0的位置空出来存储大数的长度,而不用我们的模拟函数去返回大数运算结果的长度,但是在本文中并没有这么实现,但是这也是一种不错的想法,值得一试。

大数运算核心思想

大数运算,核心思想就是模拟运算,加减乘都是直接模拟小学学的数学,除法有些特殊,使用的是减法实现(例如4/2就可以表示成4-2-2)

以下是一段除法与减法的关系,以供参考:

除法与减法之间的关系:
1、能够整除的除法
除法可以看作连续减去相同数的减法;被除数就是被减数,除数就是相同的减数,连减的最多次数就是商。
2、不能整除的除法
除法可以看作连续减去相同数的减法;被除数就是被减数,除数就是相同的减数,连减的最多次数就是商,除法的余数就是减法的最后结果数值。

具体的模拟过程在各种运算中会有讲解,这里先知道是模拟运算即可。

以下的大数运算除了除法我都没有抽象出函数来,但在理解后这是非常简单的,不会影响食用。

1.大数加法

对于加法,很明显,从低位开始加,满10 进1 模拟很简单

//这里a[] b[] 是存储要运算的两个大数 max是ab两数中较大的一个的位数 c[] 存储运算结果,并且abc所有未被使用过的位置都被初始化为0
	for(i=0;i<max;i++)
	{
		c[i]+=a[i]+b[i];
		if(c[i]>=10){//进位处理
			c[i+1]+=1;//进10位 
			c[i]%=10;//留下个位 
		}
	}
	
	//处理进位也可以单独拿出做 - 可以手动模拟一下两种方法
	/*for(i=0;i<max;i++){
		if(c[i]>=10){
			c[i+1]+=1;//进10位 
			c[i]%=10;//留下个位 
		}
	}*/

完整代码

#include<bits/stdc++.h>
//大数加法 
using namespace std;

int main(){
	
	//读入两个大数 
	string num1,num2;//字符串 利用字符数组存也是可以的 
					// 但是注意数组长度需要足够 很多大数题目数字长度都是上万的
	cin>>num1>>num2;
	
	//分别获取位数并将每一位存入整形数组中 
	int n=num1.length(),m=num2.length();//length()函数用于获取存储字符串长度
	int max= m>n?m:n +1;//两个数相加  位数不会超过较大的数的位数+1 
	int a[max],b[max],c[max];//每一位都初始化为0 
	//int a[max]={0},b[max]={0},c[max]={0};//每一位都初始化为0 
	/****按理来说如上的初始化是允许的,但是有的编译器
	   可能初始化出错,所以最后手动出初始化一下,或者定义全局变量了事**/
	int i,j;
	for(i=0;i<max;i++)//初始化 
		a[i]=0; 
	for(i=0;i<max;i++)//初始化 
		b[i]=0; 
	for(i=0;i<max;i++)//初始化 
		c[i]=0; 	
		
	//num1 - n位 - 将num1字符串存储的大数翻转存储(a[0]存储个位,a[1]存储十位)到int数组a中
	for(i=0,j=n-1;i<n;i++,j--)
		a[i]=num1[j]-'0';
	//num2 - m位 
	for(i=0,j=m-1;i<m;i++,j--)
		b[i]=num2[j]-'0';
	
	/*********上面都是利用数组储存大数,下面开始模拟大数加法*************/

	for(i=0;i<max;i++)
	{
		c[i]+a[i]+b[i];//若需要在计算的同时进行进位处理,需要写成+=
		if(c[i]>=10){//进位处理
			c[i+1]+=1;//进10位 
			c[i]%=10;//留下个位 
		}
	}
	
	//处理进位也可以单独拿出做 - 可以手动模拟一下两种方法
	/*for(i=0;i<max;i++){
		if(c[i]>=10){
			c[i+1]+=1;//进10位 
			c[i]%=10;//留下个位 
		}
	}*/
	
	i=max-1;
	while(c[i]==0)//去除答案数组c中高位上的0
		i--;
	while(i>=0)//c[i]是从高位向前看第一个不为0的数
		printf("%d",c[i--]);
}

2.大数减法

对于减法,也很简单,从低位开始减,减不了就向前一位借1 当前位置加10 ,模拟如下:
注意:不一定要将较大的数放置在被减数的位置,但是这样方便处理

//这里a[] b[] 是存储要运算的两个大数 max是ab两数中较大的一个的位数 c[] 存储运算结果,并且abc所有未被使用过的位置都被初始化为0
	for(i=0;i<max;i++){
		//处理借位
		if(a[i]<b[i]>){
			a[i]+=10;
			a[i+1]--;
		}
		
		c[i]=a[i]-b[i];
	}
	
	
	//处理借位可以单独拿出来 - 可以手动模拟一下两种方法
	/*for(i=0;i<max;i++){
		if(c[i]<0){
			c[i]+=10;
			c[i+1]--;
		}
	}*/
#include<bits/stdc++.h>
//大数减法 
using namespace std;

int main(){
	//读入两个大数 
	string num1,num2;
	
	//cin>>num1>>num2;
	num1="9823465789117604259074834";
	num2="31468761323431";
	//差  9823465789086135497751403
	//分别获取位数并将每一位存入整形数组中 
	int n=num1.length(),m=num2.length();
	int max= m>n?m:n ;//两个数相减  位数不会超过较大的数的位数 
	int a[max],b[max],c[max];//每一位都要初始化为0 
		
	//将位数较大的数放在第一个 我们始终用绝对值较大的数减去绝对值较小的数
	if(n<m){
		string temp = num1;
		num1=num2;
		num2=temp;
		int tmp = n;
		n=m;
		m=tmp;
	}

	//int a[max]={0},b[max]={0},c[max]={0};//每一位都初始化为0 不这样做的理由同上
	int i,j;
	for(i=0;i<max;i++)//初始化 
		a[i]=0; 
	for(i=0;i<max;i++)//初始化 
		b[i]=0; 
	for(i=0;i<max;i++)//初始化 
		c[i]=0; 
		
	//num1 - n位 - 将num1字符串存储的大数翻转存储(a[0]存储个位,a[1]存储十位)到int数组a中
	for(i=0,j=n-1;i<n;i++,j--)
		a[i]=num1[j]-'0';
	//num2 - m位 
	for(i=0,j=m-1;i<m;i++,j--)
		b[i]=num2[j]-'0';
		
	/********以上都是在存储大数,以下开始模拟减法*********/	

	for(i=0;i<max;i++){
		//处理借位
		if(a[i]<b[i]>){
			a[i]+=10;
			a[i+1]--;
		}
		
		c[i]=a[i]-b[i];
	}
	
	
	//处理借位可以单独拿出来 - 可以手动模拟一下两种方法
	/*for(i=0;i<max;i++){
		if(c[i]<0){
			c[i]+=10;
			c[i+1]--;
		}
	}*/
	
	i=max-1;
	while(c[i]==0)//去除答案数组c中高位上的0
		i--;
	while(i>=0)
		printf("%d",c[i--]);

}

3.辨识负数的大数加减法

这里只是一个个人的尝试,在一个函数中处理符号和加减法问题 ,但实用价值不大,可跳过。

#include<bits/stdc++.h>
//辨识负数的大数加法 
using namespace std;

int main(){
	
	//读入两个大数 
	string num1,num2;
	
	//cin>>num1>>num2;
	num1="9823465789117604259074834";
	num2="-31468761323431";
	//和  9823465789149073020398265  差  9823465789086135497751403
	//分别获取位数并将每一位存入整形数组中 
	int n=num1.length(),m=num2.length();
	
 
	
	int fg1,fg2,fg;//符号位 
	fg1=(num1[0]=='-')?-1:1;
	n=(fg1==-1)?n-1:n;//负号则总位数减一 
	fg2=(num2[0]=='-')?-1:1;
	m=(fg2==-1)?m-1:m;//负号则总位数减一 
	fg=(fg1*n+fg2*m>0)?1:-1;
	
	//将位数较大的数放在第一个 
	if(n<m){
		string temp = num1;
		num1=num2;
		num2=temp;
		int tmp = n;
		n=m;
		m=tmp;
	}
	
	int max= m>n?m:n +1;//两个数相加减  位数不会超过较大的数的位数+1 
	
	int a[max],b[max],c[max];//每一位都初始化为0 
	//int a[max]={0},b[max]={0},c[max]={0};//每一位都初始化为0 
	int i,j;
	for(i=0;i<max;i++)//初始化 
		a[i]=0; 
	for(i=0;i<max;i++)//初始化 
		b[i]=0; 
	for(i=0;i<max;i++)//初始化 
		c[i]=0; 
		
	//num1 - n位 
	for(i=0,j=n-1+(fg1==-1?1:0);i<n;i++,j--)
		a[i]=num1[j]-'0';
	//num2 - m位 
	for(i=0,j=m-1+(fg2==-1?1:0);i<m;i++,j--)
		b[i]=num2[j]-'0';
	
	/*******到此为止以上都是大数的保存,下面将模拟大数加减法的运算***********/	

	for(i=0;i<max;i++)
		if(fg1*fg2==1)//同号则当正数加法来做 
			c[i]=a[i]+b[i];
		else//异号则是减法 
			c[i]=a[i]-b[i];//拿较大数每一位减去小数每一位 
	
	//处理进位
	for(i=0;i<max;i++){
		if(c[i]>=10){
			c[i+1]+=1;//进10位 
			c[i]%=10;//留下个位 
		}else if(c[i]<0){
			c[i+1]-=1;
			c[i]+=10;
		}
	}
	
	i=max-1;
	while(c[i]==0)
		i--;
	
	if((fg1==-1&&fg2==-1)||fg==-1)
		printf("-");
	while(i>=0)
		printf("%d",c[i--]);
}

4.大数乘法

对于乘法的模拟需要一定的理解,其中最重要的代码既是c[i+j]+=a[i]*b[j];,意思是a的第i位乘以b的第j位要存在c的第i+j位上,可以自己画一个乘法的列式,确实如此。

乘法模拟:

//定义第三个数组来存储结果
	int c[3000]={0};		
	for(i=0;i<n;i++)
		for(j=0;j<m;j++){
			c[i+j]+=a[i]*b[j];
		} 
		
	//处理进位的情况
	for(i=0;i<n+m;i++){
		if(a[i]>=10){
			c[i+1]+=c[i]/10;
			c[i]%=10;
		}
	}

完整代码

#include<bits/stdc++.h> 

using namespace std;

int main(){
	 //用字符串读入两个大数 
	string num1 ,num2;
	cin>>num1>>num2;
	//获取两个大数的位数 
	int n=num1.length(),m=num2.length();
	int a[n],b[m];//定义两个整形数组用于待会存储这两个大数的每一位 

	int i,j;
	//用整形数组从低位到高位存储这两个大数 
	for(i=0,j=n-1;i<n;i++,j--)
		a[i]=num1[j]-'0';
	for(i=0,j=m-1;i<m;i++,j--)
		b[i]=num2[j]-'0';
		
	/*******到此为止以上都是大数的保存,下面将模拟大数乘法的运算***********/


	//定义第三个数组来存储结果
	int c[3000]={0};		
	for(i=0;i<n;i++)
		for(j=0;j<m;j++){
			c[i+j]+=a[i]*b[j];
		} 
		
	//处理进位的情况
	for(i=0;i<n+m;i++){
		if(a[i]>=10){
			c[i+1]+=c[i]/10;
			c[i]%=10;
		}
	}
	
	//打印结果
	for(j=2999;j>0;j--){
		if(c[j]!=0)
			break;
	} 
	
	for(i=j;i>=0;i--)
		printf("%d",c[i]);
	printf("\n");
	
	return 0;
}

5.大数除法

对于大数除法,我们一开始就讲过,其是用减法来实现的:
例如 9999 / 30 9999 / 30 9999/30 直接除以 30 ∗ 100 30*100 30100 连除三次 商 300 300 300 999 999 999
再除以 30 ∗ 10 30*10 3010 连除 3 3 3次 商330 余 99 99 99
再除以 30 ∗ 1 30*1 301 连除 3 3 3次 商333 余 9 9 9
9 9 9除不尽 30 30 30 所以最终商为 333 333 333

#include <bits/stdc++.h>

using namespace std;

int division(string num1, string num2, int c[], int n, int m);
int substract(int a[], int b[], int n, int m);

int main()
{
    //读入两个大数
    string num1, num2;
    //cin>>num1>>num2;
    num1 = "15697445132154737464164654641";
    num2 = "13545554345798743";

    //结果:1,158,863,249,994.8900064948937468194

    //获取两个大数的长度
    int n = num1.length(), m = num2.length();
    int sum[10001];

    int lenc = division(num1, num2, sum, n, m);
    for (int i = lenc - 1; i >= 0; i--)
        printf("%d", sum[i]);

    return 0;
}

/*参数:
    a[] 除数
    b[] 被除数
    c[] 存储商的数组
    n 除数长度
    m 被除数长度
  返回值:
    商的长度
*/
int division(string num1, string num2, int c[], int n, int m)
{
    if (n < m)
        return 0; //除不尽

    int diff = n - m;

    //定义两个数组去翻转存储这两个大数
    int a[n] = {0}, b[n] = {0}, i, j;
    for (i = 0, j = n - 1; j >= 0; i++, j--)
        a[i] = num1[j] - '0';
    for (i = diff, j = m - 1; j >= 0; i++, j--) //注意这里b的存储方式是将b扩大了10^diff倍
        b[i] = num2[j] - '0';

    cout << endl;

    for (i = 0; i < n; i++) //数组c全部初始化为0
        c[i] = 0;

    int temp; //接收substact 函数返回参数
    m = n;
    for (i = 0; i <= diff; i++)
        while ((temp = substract(a, b + i, n, m - i)) >= 0) //b+i相当于b/(i^10)
        {
            n = temp;
            c[diff - i]++;
        }
    cout << endl;

    //计算长度
    for (i = diff; c[i] == 0 && i >= 0; i--)
        ;
    return i + 1;
}

/*
 参数:
    a[] 减数
    b[] 被减数
    n 减数长度
    m 被减数长度
  返回值:
    结果的长度
    >
*/
int substract(int a[], int b[], int n, int m)
{
    if (n < m)
        return -1;
    int i;
    if (n == m)                      //判断 a > b
        for (i = n - 1; i >= 0; i--) //丛大位开始比较
        {
            if (a[i] > b[i]) //等则继续比 大则a>b
                break;
            else if (a[i] < b[i])
                return -1; // a < b
        }

    for (i = 0; i <= n - 1; i++)
    {
        a[i] -= b[i];
        if (a[i] < 0)
        {
            a[i + 1]--;
            a[i] += 10;
        }
    }

    for (i = n - 1; i >= 0; i--) // 查找结果的最高位
    {
        if (a[i])           //最高位第一个不为0
            return (i + 1); //得到位数并返回
    }
    return 0; //两数相等的时候返回0
}

6.高精度相关的算法题


都看到这里了,各位哥哥姐姐叔叔阿姨给小王点个赞 关个注 留个言吧,和小王一起成长吧,你们的关注是对我最大的支持。
有事没事进来看看吧 : 小王的博客目录索引
专栏看这 : 数据结构与算法专栏


如果以上内容有任何不准确或遗漏之处,或者你有更好的意见,就在下面留个言让我知道吧-我会尽我所能来回答。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 黑客帝国 设计师:白松林 返回首页