大数的基本运算
寒假新队员训练计划。
在讲到大数运算前我们先回顾一下我们常用的变量类型的数值范围
类型名称 字节数 取值范围
short int 2 -2^14 ~ 2^14-1
int 4 -2^31 ~ 2^31-1
unsigned int 4 0 ~ 2^32-1
long long 8 -2^63 ~ 2^63-1
unsigned long long 8 0 ~ 2^64-1 0 ~ 18446744073709551615
从中我们可以看到,即使是 unsigned long long ,最大也只能存储 1e19 左右的数
而如果我们被要求进行远大于 1e19 的数的运算,那么常规的做法就无法操作
所以我们引入了一个新的概念——大数
我们可以这么定义它:无法用常规整(浮点)型变量存储,无法进行简单符号运算的数
如:123456789123456789123456789123456789123456789,它就为一个大数
当然还有一些高进制数也可当做大数,如abc,我们可以将它看成26进制下的数。它的运算规则也和我们下面提及差不多
好了那么现在问你,给你两个大数,要求你对它进行简单(加减乘除)运算,你会怎么做呢?
大数运算模拟
首先我们要考虑如何来把这个数读入并储存。因为是大数,我们无法用以往的int、long long甚至unsigned long long储存
所以我们得先用字符数组对它进行储存。
我们把该大数每一位分解开来分别存到字符数组的每个位置
假设我们用来储存的字符串为S,则对大数“12345678912345678912345”的储存方式为
s[1] = '1' , s[2] = '2' , s[3] = '3' , s[4] = '4' , s[5] = '5' , s[6] = '6' , s[7] = '7' , s[8] = '8' , s[9] = '9' , s[10] = '1' , s[11] = '2' ......
好了我们完成了第一步。
接着我们要对它进行运算。
若题目要求的是对两个大数进行加法运算,那么我们该怎么做呢?(设我们已经用s1、s2数组分别储存了两个大数)
我们知道大数是用字符数组储存的,所以无法像常规一样直接用 “ + ”来操作。
这时我们可以回想小学数学教的进位加法(从个位开始逐位相加)某一位的结果每大于等于10,则需要向上一位进1
如图 ——
好了那么现在怎么操作我们已经知道了,而在开始操作之前我们需要注意什么呢?
在开始加前我们要注意两点
①、我们要进行加法运算,而这时候用来储存的还是字符数组,所以要先把字符数组转为整型数组
②、我们是顺序储存的,所以这时候大数的个位就对应字符数组的最后一位,所以我们需要从后往前模拟进位加法
好了上代码
#include<bits/stdc++.h> using namespace std; const int N = 2e5 + 10; char s1[N] , s2[N]; int a[N] , b[N]; int ans[N]; int main() { while(~scanf("%s %s" , s1 + 1 , s2 + 1)) { memset(a , 0 , sizeof(a)); memset(b , 0 , sizeof(b)); memset(ans , 0 , sizeof(ans)); int len1 = strlen(s1 + 1) , len2 = strlen(s2 + 1); //把读入的数颠倒过来同时转为 int 数组储存 //颠倒的目的便于模拟进位加法,此时a , b 数组的首位代表大数的个位 for(int i = 1 ; i <= len1 ; i ++) a[len1 - i + 1] = s1[i] - '0'; for(int i = 1 ; i <= len2 ; i ++) b[len2 - i + 1] = s2[i] - '0'; // 加一是因为最高位相加有可能使最高位增加,若 99 11 的最高位为2 , 相加后为 110 最高位为 3 int LEN = max(len1 , len2) + 1; for(int i = 1 ; i <= LEN ; i ++) { //保留 相加后的个位部分 ans[i] += (a[i] + b[i]) % 10; // 若第i位结果大于 10 则 i + 1 位进位 1 ans[i + 1] += (a[i] + b[i]) / 10; } // 去前导 0,如 1+7 = 8,则ans[1] = 8,ans[2] = 0,我们最后的答案为8而不是08,所以要把前导0去掉 while(!ans[LEN]) LEN -- ; for(int i = LEN ; i > 0 ; i --) printf("%d" , ans[i]); printf("\n"); } return 0; }
以上代码只能针对非负整数使用,若要针对所有整数,我们也只要在读入的时候判断字符串首位是否有 "-"(负号)然后模拟小学学的进位加法或者进位减法(a + -b == a - b)就可以了
加、 减、乘其实都差不多,我们只要按照小学教的进位思想进行模拟就可以了
而除法则会相对难一些,用小学学的除法运算进行模拟是有些复杂的
所以我也不细讲了(懒),这里推荐大数除法 , 其也有对大数加法、减法、乘法的细讲,有兴趣的同学可以了解一下
大数运算模板
个人感觉呢大数这一块 知识点并不是很重要。 所以如果你对以上说的一头雾水,也没有关系
大数运算即使你不理解、不会写,但只要现阶段会用就很OK了
这里的用指的是套模板。很多大佬都有在网上留下一套大数运算的模板,其中一些是经过很好的优化、改良的
我们可以从中选择自己看得舒服的模板并学会 how to use。 当然最好是根据自己的需求适当修饰 ,把它变为自己的东西
下面分享一下我常用的大数模板
#include<bits/stdc++.h> #define MAXN 999999999 #define MAXSIZE 5000 #define DLEN 9 using namespace std; class BigInt { private: int a[500]; int len; bool sign; protected: BigInt add(const BigInt&,const bool)const; BigInt subtract(const BigInt&,const bool)const; public: BigInt() {sign=false;len=1;memset(a,0,sizeof(a));} BigInt(const int); BigInt(const char*); BigInt(const BigInt&); BigInt& operator=(const BigInt&); friend istream& operator>>(istream&,BigInt&); friend ostream& operator<<(ostream&,const BigInt&); friend BigInt abs(const BigInt&); BigInt operator+(const BigInt&)const; BigInt operator-(const BigInt&)const; BigInt operator*(const BigInt&)const; BigInt operator/(const BigInt&)const; BigInt operator/(const int&)const; BigInt operator^(const int&)const; int operator%(const int&)const; bool operator<(const BigInt&)const; bool operator>(const BigInt&)const; bool operator<=(const BigInt&)const; bool operator>=(const BigInt&)const; bool operator==(const BigInt&)const; inline int size() {return DLEN*(len-1)+to_string(a[len-1]).size();} }; BigInt::BigInt(const int b) { int c,d=abs(b); len=0; sign=b<0; memset(a,0,sizeof(a)); while(d>MAXN) { c=d-(d/(MAXN+1))*(MAXN+1); d=d/(MAXN+1); a[len++]=c; } a[len++]=d; } BigInt::BigInt(const char *in) { const char *s; if(in[0]=='-') s=in+1,sign=true; else s=in,sign=false; int t,k,index,L,i; memset(a,0,sizeof(a)); L=strlen(s); len=L/DLEN; if(L%DLEN) len++; index=0; for(i=L-1;i>=0;i-=DLEN) { t=0; k=i-DLEN+1; if(k<0) k=0; for(int j=k;j<=i;j++) t=t*10+s[j]-'0'; a[index++]=t; } } BigInt::BigInt(const BigInt& T):len(T.len),sign(T.sign) { memset(a,0,sizeof(a)); for(int i=0;i<len;i++) a[i]=T.a[i]; } BigInt& BigInt::operator=(const BigInt& n) { sign=n.sign; len=n.len; memset(a,0,sizeof(a)); for(int i=0;i<len;i++) a[i]=n.a[i]; return *this; } istream& operator>>(istream& in,BigInt& b) { char ch[MAXSIZE]; in>>ch; b=BigInt(ch); return in; } ostream& operator<<(ostream& out,const BigInt& b) { out<<(b.sign?"-":"")<<b.a[b.len-1]; for(int i=b.len-2;i>=0;i--) out<<setw(DLEN)<<setfill('0')<<b.a[i]; return out; } BigInt abs(const BigInt& T) { BigInt t(T); t.sign=false; return t; } BigInt BigInt::add(const BigInt& T,const bool flag)const { BigInt t(*this); t.sign=flag; int big; big=T.len>len?T.len:len; for(int i=0;i<big;i++) { t.a[i]+=T.a[i]; if(t.a[i]>MAXN) { t.a[i+1]++; t.a[i]-=MAXN+1; } } if(t.a[big]!=0) t.len=big+1; else t.len=big; return t; } BigInt BigInt::subtract(const BigInt& T,const bool flag)const { BigInt t(*this); t.sign=flag; for(int i=0;i<t.len;i++) { if(t.a[i]<T.a[i]) { int j=i+1; while(t.a[j]==0) j++; t.a[j--]--; while(j>i) t.a[j--]+=MAXN; t.a[i]+=MAXN+1-T.a[i]; } else t.a[i]-=T.a[i]; } while(t.a[t.len-1]==0&&t.len>1) t.len--; return t; } BigInt BigInt::operator+(const BigInt& T)const { BigInt ret; if(sign^T.sign) { BigInt t1(*this),t2(T); if(abs(t1)<abs(t2)) swap(t1,t2); ret=t1.subtract(t2,t1.sign); if(ret.a[ret.len-1]==0&&ret.len==1) ret.sign=false; } else ret=this->add(T,sign); return ret; } BigInt BigInt::operator-(const BigInt& T)const { BigInt ret; if(sign^T.sign) ret=this->add(T,sign); else { BigInt t1(*this),t2(T); bool sn; if(abs(t1)<abs(t2)) { sn=t1.sign^1; swap(t1,t2); } else sn=t1.sign; ret=t1.subtract(t2,sn); if(ret.a[ret.len-1]==0&&ret.len==1) ret.sign=false; } return ret; } BigInt BigInt::operator*(const BigInt& T)const { BigInt ret; long long up; long long temp,temp1; for(int i=0;i<len;i++) { up=0; for(int j=0;j<T.len;j++) { temp=(long long)a[i]*T.a[j]+ret.a[i+j]+up; if(temp>MAXN) { temp1=temp%(MAXN+1); up=temp/(MAXN+1); ret.a[i+j]=temp1; } else { up=0; ret.a[i+j]=temp; } } if(up!=0) ret.a[i+T.len]=up; } ret.sign=sign^T.sign; ret.len=len+T.len; while(ret.a[ret.len-1]==0&&ret.len>1) ret.len--; return ret; } BigInt BigInt::operator/(const BigInt& T)const { BigInt r(*this),ret(0); while(r>=T) { BigInt down(1); while(r-T*down>=0) down=down*10; down=down/10; r=r-T*down; ret=ret+down; } ret.sign=sign^T.sign; return ret; } BigInt BigInt::operator/(const int &b)const { BigInt ret; bool sign1=b<0; long long down=0; for(int i=len-1;i>=0;i--) { ret.a[i]=(a[i]+down*(MAXN+1))/b; down=a[i]+down*(MAXN+1)-(long long)ret.a[i]*b; } ret.sign=sign^sign1; ret.len=len; while(ret.a[ret.len-1]==0&&ret.len>1) ret.len--; return ret; } int BigInt::operator%(const int &b)const { int d=0; for(int i=len-1;i>=0;i--) d=(((long long)d*(MAXN+1))%b+a[i])%b; return sign?-d:d; } BigInt BigInt::operator^(const int &n)const { BigInt ret(1),t(*this); int m=n; while(m) { if(m&1) ret=ret*t; t=t*t; m>>=1; } return ret; } bool BigInt::operator<(const BigInt& T)const { if(sign&&!T.sign) return true; if(!sign&&T.sign) return false; //Ö»ÓÐsignÏàͬ²ÅÄÜ×÷±È½Ï~~ if(len!=T.len) return sign^(len<T.len); for(int ln=len-1;ln>=0;ln--) if(a[ln]!=T.a[ln]) return sign^(a[ln]<T.a[ln]); return false; } bool BigInt::operator>(const BigInt& T)const { return T<*this; } bool BigInt::operator<=(const BigInt& T)const { if(*this==T) return true; return *this<T; } bool BigInt::operator>=(const BigInt& T)const { return T<=*this; } bool BigInt::operator==(const BigInt& T)const { if(sign!=T.sign||len!=T.len) return false; for(int i=len-1;i>=0;i--) if(a[i]!=T.a[i]) return false; return true; } int main() { BigInt a , b; while(cin >> a >> b) { cout << a + b << '\n'; cout << a - b << '\n'; cout << a * b << '\n'; cout << a / b << '\n'; } return 0; }
Java大数类
Java是一门对大数很友好的编程语言,所以掌握好java的大数类是很有帮助的
如果你完全没接触 Java 也没有关系,因为编程语言的语法差别并不会很大(所以你只要花几分钟看一下java的入门语法就可以了)
并且这里我们只提及 Java 的大数类,所以应该是难不倒大家的。
好了假设你已经会了java的基本语法,然后我们开始进入正题
在Java 中有两个类:BigInteger、BigDecimal 分别表示大整数类和大浮点数类。其理论上能表示无限大的数(只要内存不爆)
balabalabala,好了让我们看看how to use 大数类。
直接上代码吧(因为 BigInteger 和 BigDecimal 的大多用法都相同,所以这里我只拿 BigInteger 做举例)
import java.util.*; import java.math.*; //大数类存在于这个包中 public class Main{ public static void main(String[] args){ Scanner cin = new Scanner(System.in); int e = 10; BigInteger a, b; BigInteger ans_add, ans_sub, ans_mul, ans_div, ans_mod , ans_change , ans_abs , ans_pow; a = cin.nextBigInteger(); b = cin.nextBigInteger(); ans_add = a.add(b); //a+b ans_sub = a.subtract(b); //a-b ans_mul = a.multiply(b); //a*b ans_div = a.divide(b); //a/b ans_mod = a.mod(b); //a%b ans_change = BigInteger.valueOf(e); //转换、赋值 将e的值赋给ans_change,e为int ans_abs = a.abs(); // a的绝对值 ans_pow = a.pow(e); // a的e次幂 , e 为 int System.out.println("a + b = " + ans_add); System.out.println("a - b = " + ans_sub); System.out.println("a * b = " + ans_mul); System.out.println("a / b = " + ans_div); System.out.println("a % b = " + ans_mod); System.out.println("ans_change = " + ans_change); System.out.println("a的绝对值 = " + ans_abs); System.out.println("a的e次幂 = " + ans_pow); if (a.compareTo(b) == 0) //比较a和b System.out.println("相等"); else System.out.println("不相等"); System.out.println(a.toString()); //将大数a转字符串输出 int p = 8; System.out.println(a.toString(p)); //将大数a转换成p进制后 按字符串输出 } }
总结
大数运算其实只要能算出正确结果就好了,至于怎么写代码运算呢?我觉得还是不要写了,套模板或用Java处理是完全OK的(理直气壮!)