高精度专题

高精度


高精度的出现是为了解决计算机所不能存储的数据范围。当数字超出了这个范围,我们必须用一些算法来模拟人们计算的过程。

高精度数字的存储方法

通常采用数组存储数字的每一位。要注意的是尽量采用倒序存储,这是为了考虑进位问题:进位可以直接插到后面。

A+B的问题

问题分析

高精度加法是一个最简单的高精度问题:
每一位相加的时候需要加三个量:

  1. A[i]----A第i位上的数字
  2. B[i]----B第i位上的数字
  3. t----从A[i]+B[i]上获得的进位
    t从整除算出
    ans[i]从取模算出

AC代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define in(x) scanf("%d",&x)
#define debug(x) cerr<<#x<<" : "<<x<<endl
using namespace std;
const int N=100005;
int a[N],b[N];

int main(){
    string aa,bb;
    cin>>aa>>bb;
    for(int i=aa.length()-1,j=0;i>=0;i--,j++){
        a[j]=aa[i]^48;
    }
    for(int i=bb.length()-1,j=0;i>=0;i--,j++){
        b[j]=bb[i]^48;
    }
    int n=0;
    int i=0;
    int t=0;
    while(i!=max(aa.length(),bb.length())){
        int now=a[i]+b[i]+t;
        a[i]=(now)%10;
        t=now/10;
        if(t&&i==max(aa.length(),bb.length())-1){
            a[i+1]=t;
            n++;
        }
        i++;
        n++;
    }
    
    for(int i=n-1;i>=0;i--){
        cout<<a[i];
    }
    cout<<endl;
    
    return 0;
}

A-B问题

问题分析

与加法一样,减法要考虑三个量。
不过除去这三个数之外,要判断数的大小,若A<B,则让AB互换,输出“-”。(这里有个自己菜的坑,下意识认为数的大小可以用字典序比较。。。)
除去这个,还有一个最终答案先导0的问题。
这里先去除先导零,在输出答案。不过要注意的是,个位数也是先导零必须输出。

AC代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define debug(x) cerr<<#x<<" : "<<x<<endl
using namespace std;
const int N=100005;

int a[N],b[N];


bool cmp(string a,string b){
    if(a.length()==b.length()){
        for(int i=0;i<a.length();i++){
            if(a[i]==b[i]){
                continue;
            }
            return a[i]>b[i];
        }
        return true;
    }
    else{
        return a.length()>b.length();
    }
}



int main(){
	bool pos=true;
	string aa,bb;
	cin>>aa>>bb;
	if(!cmp(aa,bb)){
		string t=aa;
		aa=bb;
		bb=t;
		pos=false;
	}
	for(int i=aa.length()-1,j=0;i>=0;i--,j++){
		a[j]=aa[i]^48;
	}
	for(int i=bb.length()-1,j=0;i>=0;i--,j++){
		b[j]=bb[i]^48;
	}
	int len=max(aa.length(),bb.length());
	int i=0;
	int t=0;
	int n=len;
	while(i!=len){
		if(a[i]-b[i]-t>=0){
			a[i]=a[i]-b[i]-t;
			t=0;
		}
		else{
			a[i]=a[i]+10-b[i]-t;
			t=1;
		}
		i++;
	}
	if(!pos){
		cout<<"-";
	}
	int tag=0;
	for(int i=n-1;i>=0;i--){
		if(a[i]==0&&tag==0){
		    if(i==0){
		        cout<<0;
		    }
			continue;
		}
		cout<<a[i];
		tag=1;
	}
	cout<<endl;
	return 0;
}

A*a问题(一个大整数乘一个普通整数)

问题分析

大整数各位0123
整数12
答案位1476
进位1230

上表是一个相乘的过程:
答案位的计算过程:(大整数位乘以整数+进位)%10
下一位进位计算过程:(大整数位乘以整数+进位)/ 10

!!同样与减法一样有先导0的问题;

AC代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define debug(x) cerr<<#x<<" : "<<x<<endl
using namespace std;
const int N=100010;

int a[N];

int main(){
	string aa;
	cin>>aa;
	for(int i=aa.length()-1,j=0;i>=0;i--,j++){
		a[j]=aa[i]^48;
	}
	int k;
	cin>>k;
	int n=0;
	for(int i=0,t=0;i<aa.length();i++){
	   // debug(a[i]);
		int tt=(a[i]*k+t);
// 		debug(tt);
		a[i]=tt%10;
// 		debug(a[i]);
		t=tt/10;
// 		debug(t);
		if(i==aa.length()-1&&t){
			a[i+1]=t;
			n++;
		}
		n++;
	}
	int tag=0;
	for(int i=n-1;i>=0;i--){
	    if(tag==0&&a[i]==0){
	        if(i==0){
	            cout<<0;
	        }
	        continue;
	    }
		cout<<a[i];
		tag=1;
	}
	cout<<endl;
	return 0;
}

A/a问题(一个大整数除一个普通整数)

问题分析

太简单,回头再写

AC代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int N=100010;

int a[N];

int main(){
	string aa;
	cin>>aa;
	for(int i=aa.length()-1,j=0;i>=0;i--,j++){
		a[j]=aa[i]^48;
	}
	int k;
	cin>>k;
	int t=0;
	for(int i=aa.length()-1;i>=0;i--){
		int tt=a[i]+t*10;
		a[i]=(tt)/k;
		t=tt%k;
	} 
	int tag=0;
	for(int i=aa.length()-1;i>=0;i--){
		if(tag==0&&a[i]==0){
			if(i==0){
				cout<<0;
			}
			continue;
		}
		cout<<a[i];
		tag=1;
	}
	cout<<endl<<t<<endl;
	return 0;
}

高精度乘高精度

思路分析

高精度乘以高精度利用分支法的思想,将每个数字分为前半部分和后半部分,假设数一为n位,数二位m位,即:
( A n 2 + B ) (A^\frac{n}{2}+B) (A2n+B)
( C m 2 + D ) (C^\frac{m}{2}+D) (C2m+D)
其相乘结果为:(暂记公式1)
( A n 2 C m 2 + B D ) + ( A n 2 D + C m 2 B ) (A^\frac{n}{2}C^\frac{m}{2}+BD)+(A^\frac{n}{2}D+C^\frac{m}{2}B) (A2nC2m+BD)+(A2nD+C2mB)
此公式可以变形为:(暂记公式2)
2 ( A n 2 C m 2 + B D ) + ( A n 2 − B ) ( C m 2 − D ) 2(A^\frac{n}{2}C^\frac{m}{2}+BD)+(A^\frac{n}{2}-B)(C^\frac{m}{2}-D) 2(A2nC2m+BD)+(A2nB)(C2mD)
我们可以想到,分治后会有以下几种情况:

  • 两数字长度为1
  • 其中只有以个数字长度为1
  • 都不为1
    且第二种情况还可以分为两种情况
    • 有进位
    • 无进位

上代码-----------------------------------------------------------------------

代码

代码1(由公式1推导而来)
#include <iostream>
#include <string>
#include <sstream>

using namespace std;

string multi(string A, string B); //计算大整数相乘
string Plus(string q, string w, string e, string r); //计算大整数相加
stringstream ss;

int main() {
	string A, B;

	while (cin >> A >> B) {
		cout << multi(A, B) << endl;
	}
	return 0;
}

string multi(string A, string B) {
	int len_A = A.length();
	int len_B = B.length();
	if (len_A == 1) {
		if (len_B == 1) { //最基本的情况:A和B都是一位数,把A、B从string转为int(我这里用的stringstream),然后相乘后转回为string型return回去。
			ss << A;
			int a;
			ss >> a;
			ss.clear();
			ss << B;
			int b;
			ss >> b;
			ss.clear();
			ss << b*a;
			string str_out;
			ss >> str_out;
			ss.clear();
			return str_out;
		}
		else { //A是个位数,B不是的情况下,按照分治的思想把B分开分别与A相乘。
			string B1, B2;
			B1 = B.substr(0, len_B / 2);
			B2 = B.substr(len_B / 2);
			string AB1 = multi(A, B1);
			string AB2 = multi(A, B2);
			if (AB2.length() > B2.length()) {
				string str = AB2.substr(0, 1);
				ss << str;
				int ab2;
				ss >> ab2;
				ss.clear();
				ss << AB1;
				int ab1;
				ss >> ab1;
				ss.clear();
				ss << ab1 + ab2;
				ss >> AB1;
				ss.clear();
				return AB1 + AB2.substr(1);
			}
			else
				return AB1 + AB2;
		}
	}
	else {
		if (len_B == 1) {? //B是个位数,A不是的情况与上述A是个位数B不是的情况相同。
			string A1, A2;
			A1 = A.substr(0, len_A / 2);
			A2 = A.substr(len_A / 2);
			string A1B = multi(A1, B);
			string A2B = multi(A2, B);
			if (A2B.length() > A2.length()) {
				string str = A2B.substr(0, 1);
				ss << str;
				int a2b;
				ss >> a2b;
				ss.clear();
				ss << A1B;
				int a1b;
				ss >> a1b;
				ss.clear();
				ss << a1b + a2b;
				ss >> A1B;
				ss.clear();
				return A1B + A2B.substr(1);
			}
			else {
				return A1B + A2B;
			}
		}
		else { //A和B都不是个位数,就按照上述方法分治就可以了,只是为了最后相加的时候方便,把返回的四个部分都用0凑成了位数相同的。
			string A1, A2, B1, B2;
			A1 = A.substr(0, len_A / 2);
			A2 = A.substr(len_A / 2);
			B1 = B.substr(0, len_B / 2);
			B2 = B.substr(len_B / 2);
			string part1_ = multi(A1, B1);
			string part1_0(A2.length()+B2.length(), '0');//1. 补零但没有补全 
			part1_ = part1_ + part1_0;
			string part2_ = multi(A2, B2);
			string part2_00(part1_.length() - part2_.length(), '0');// 2. 补1没补全的零 
			part2_ = part2_00 + part2_;
			string part3_ = multi(A1, B2);
			string part3_0(A2.length(), '0');
			part3_ = part3_ + part3_0;
			string part3_00(part1_.length() - part3_.length(), '0');
			part3_ = part3_00 + part3_;
			string part4_ = multi(A2, B1);
			string part4_0(B2.length(), '0');
			part4_ = part4_ + part4_0;
			string part4_00(part1_.length() - part4_.length(), '0');
			part4_ = part4_00 + part4_;
			return Plus(part1_, part2_, part3_, part4_);
		}
	}
}

string Plus(string q, string w, string e, string r) { //大整数相加
	int len_q = q.length();
	string y, out;
	int a, b, c, d;
	for (int i = 1; i <= len_q; i++) {
		ss << q.substr(len_q - i, 1);
		ss >> a;
		ss.clear();
		ss << w.substr(len_q - i, 1);
		ss >> b;
		ss.clear();
		ss << e.substr(len_q - i, 1);
		ss >> c;
		ss.clear();
		ss << r.substr(len_q - i, 1);
		ss >> d;
		ss.clear();
		ss << a + b + c + d;
		ss >> y;
		ss.clear();
		if (i == 1)
			out = y;
			else if (out.length() > i - 1) {
			ss << out.substr(0, 1);
			ss >> a;
			ss.clear();
			ss << y;
			ss >> b;
			ss.clear();
			ss << a + b;
			ss >> y;
			ss.clear();
			out = y + out.substr(1);
		}
		else {
			out = y + out;
		}
	}
    return out;
}
代码2(由公式2推导而来)

此代码要比一复杂度低,因为其在计算过程中有几步的乘法计算是相同的,可以进行存值保存,减少计算次数。
且注意此代码与上面代码1同样都用到了前四种高精度计算,不会的要看前面。

//太复杂回头再码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值