• 高精度模板


    高精度

    普通的long long只能存64位,可64位以上的数怎么存储呢,又怎么运算呢,这里就引入高精度。

    这里博主用一个结构体来完成,用结构体有个好处,如有一些题需要用高精,我们可以像平常一样处理;

    存储

    struct bignum{
    	
    	ll n[N>>2];
        bool op=0;//1 +,0 -
        const ll mod=1e8;
    
    这里作者压8位存储,即数组的每个位置都存一个八位数,且是倒序存储,并用0这个位置存储数组长度。op是这个数的符号,1为正,0为负。mod是在进位是用到的,压一位时就除以10,压8位是就除以1e8(意思是10^8)。举个例子:
    1234597812345678123
    0  3
    1  45678123
    2  45978123
    3  123//这里的0、1、2、3即指数组的零号位置、一号位置,二号位置、三号位置 
    

    输出

    	void out()
    	{
                 if(!op&&(n[1]||n[0]!=1)) cout<<"-",op=0;
                 printf("%lld",n[n[0]]);
                 for(int i=n[0]-1;i>0;i--) printf("%08lld",n[i]);
                 cout<<endl;
            }
    
    如果op是0(!op) 并且n[1] 不为0或n[0]不为1,则输出负号,排除了符号为正和数字为0的情况。然后输出n[n[0]],也就是最高位,其余按8位输出,不满八位补0。

    因为是倒序存入,所以是倒序输出。而倒序的原因是因为方便进行运算。

    复制(这里相当于’=‘)

    void cpy(bignum a)
    {
        	for(int i=a.n[0];i>n[0];i--) n[i]=0;
        	for(int i=0;i<=a.n[0];i++) n[i]=a.n[i];
        	op=a.op;
    }
    
    这里比较好理解,第一个for实现了把其余的元素清0,第二个for实现了复制,然后把符号也复制过来。

    比较

    int cmp(bignum a)
    {
        	if(n[0]>a.n[0]) return 1;
        	if(n[0]<a.n[0]) return -1;
        	for(int i=n[0];i;i--)
        	{
        		if(n[i]>a.n[i]) return 1;
        		if(n[i]<a.n[i]) return -1;
    	}
    	return 0;
    }
    
    这里是一个比较函数,注意,这里比较的是绝对值,所以并没有符号的比较。总体思路是先比较长度,在比较每一位(倒序比较),把*this与a作比较,>a返回1,<a返回-1,=a返回0;

    输入

    void init()
    {
        	string ss;
        	cin>>ss;
        	if(ss[0]=='-') ss[0]='0',op=0;
        	int len=ss.length();
        	for(int i=len-1;i>=0;i-=8)
        	{
        		ll pw=1;
        		for(int j=i;j>i-8&&j>=0;j--)
        		{
        			n[n[0]]+=(ss[j]^48)*pw;
        			pw=(pw<<3)+(pw<<1);
    		}
    		++n[0];
    	}
    	n[0]--;
    }
    
    定义一个数组,cin输入,然后判断开头是否为负号,如果是,则把原来负号在的位置改成‘0’,并让op=0。
    接下来用len来存储ss的长,倒序存储fori 用来枚举开始的位置,而forj表示从i开始,倒序访问往后8个字符,并把该字符转化成对应位置的数字,pw在这里的作用是让数字对应好他的位数。pw=(pw<<3)+(pw<<1)就等价于pw*=10;处理完后n0++, 最后n0--

    因为有构造函数的存在,n0的初始值为1,所有函数最后n0会比正常值多1

    交换

    	void sp(bignum& b)
    	{
    		bignum c;
    		c.cpy(*this);
    		this->cpy(b);
    		b.cpy(c);
    	}
    
    交换函数,没什么好说的,主要说一下this,this是一个指针,指向自己,当需要引用函数时,用this->函数(),就相当于bignum c;c.函数()。

    有兴趣的读者请自行查阅

    构造函数

    	bignum ()
    	{
    		memset(n,0,sizeof(n));
    		op=1;
    		n[0]=1;
    	}
    
    构造函数,在定义时被引用,也就是说,你在定义了bignum a;那么同时a.n被清零,a.op=1,a.n[0]=1

    运算——加法

    	bignum operator + (bignum b)// a>0 b>0
    	{
    		bignum c;
    		if(op==b.op) c.op=op;
    		else
    		{
    			if(op==1)
    			{
    				b.op=1;
    				return *this-b;
    			}
    			else
    			{
    				op=1;
    				return b-*this;
    			}
    		}
    		c.n[0]=max(n[0],b.n[0])+1;
    		for(int i=1;i<=c.n[0];i++)
    		{
    			c.n[i]+=n[i]+b.n[i];
    			if(c.n[i]>=mod) c.n[i]-=mod,c.n[i+1]++;
    		}
    		while(!c.n[c.n[0]]&&c.n[0]>1) c.n[0]--;
    		return c;
    	}
    
    这里使用了重载运算符operator,顾名思义,这个赋予了‘+’另外的含义,因为它被定义在结构体bignum中,所以只有是bignum类型的,才支持这种‘+’,其他类型,比如说int,该怎么样还怎么样。
    定义一个结构体c,使得c=this+b,如果op和b的op相等的话,c的op也等于它们,否则,改变符号,改之前的正数减去负数。

    这里不用担心改完符号后两数的大小,在‘-’中会专门处理这件事。

    其余比较简单,加完进位,去前导0;

    运算——减法

    	bignum operator - (bignum b) //a>b>0
    	{
    		bignum c,d;
    		d.cpy(*this);
    		if(op!=b.op)
    		{
    			b.op^=1;
    			return *this+b;
    		}
    		else c.op=op;
    		if(this->cmp(b)==-1)
    		{
    			this->sp(b);
    			c.op^=1;
    		}
    		c.n[0]=max(n[0],b.n[0]);
    		for(int i=1;i<=c.n[0];i++)
    		{
    			c.n[i]+=n[i]-b.n[i];
    			if(c.n[i]<0) c.n[i]+=mod,c.n[i+1]--;
    		}
    		while(!c.n[c.n[0]]&&c.n[0]>1) c.n[0]--;
    		this->cpy(d);
    		return c;
    	}
    
    这里有两个结构体,c和d,c的作用和‘+’一样,而d的作用是暂时的存储一下this,原因就是在我们引用sp时会改变this的的值,那么最后我们要把它改回来。
    这里如果this与b符号不同,就把b的符号改成另一个,即1改0,0改1,这里巧妙的用到'^',0 ^0=1,0 ^ 1=1,0 ^1=1,1 ^1=0。否则c的op就和他们一样。
    如果*this比b小,交换一下两边的值,c的符号取相反。
    接下来就是减法,减去,借位,除去前导0。

    运算——乘法

    	bignum operator * (bignum b)
    	{
    		bignum c;
    		c.n[0]=n[0]+b.n[0];
    		for(int i=1;i<=n[0];i++)
    		{
    			for(int j=1;j<=b.n[0];j++)
    			{
    				c.n[i+j-1]+=n[i]*b.n[j];
    				if(c.n[i+j-1]>=mod) c.n[i+j]+=c.n[i+j-1]/mod,c.n[i+j-1]%=mod;
    			}
    		}
    		while(!c.n[c.n[0]]&&c.n[0]>1) c.n[0]--;
    		if(op!=b.op) c.op=0;
    		return c;
    	}
    
    乘法主要模拟了乘法竖式,没有什么好说的,如果符号不一样,c的符号就取负。
    当this为i,b为j时,c相对应的是i+j-1

    除法

    那么在看除法之前我们需要先看一下两个操作,左移一位(乘2),右移一位(除以2)

    左移一位

    void ly()
    {
        	++n[0];
        	for(int i=1;i<=n[0];i++)
        	{
        		n[i]<<=1;
        		if(n[i-1]>=mod) n[i-1]-=mod,++n[i];
    	}
    	if(!n[n[0]]&&n[0]>1) --n[0];
    }
    
    首先先假设*2后有进位,然后对数组每8位进行左移一位,处理进位,最后去除前导0。

    这里倒序和正序没什么区别

    右移一位

    void ry()
    {
        	for(int i=n[0];i;--i)
        	{
        		if((n[i]&1)&&i>1) n[i-1]+=mod;
        		n[i]>>=1;
    	}
    	if(!n[n[0]]&&n[0]>1) --n[0];
    }
    
    这里略有一些麻烦,for循环语句1的意思是:如果n[i]的二进制的最后一位是1(n[i]&1)且 i-1>0(i>1) ,那么在这种情况下,ni是一个奇数,右移一位是除以二,若是奇数直接除以2的话会少去余数1,,所以把ni减1,ni-1加上mod,然后右移一位。最后去除前导0

    位运算比平常的乘除快

    然后我们来看一下除法

    运算——除法

    	bignum operator / (bignum b)
    	{
    		bignum cp,lt,c,d;
    		d.cpy(*this);
    		if(op!=b.op) lt.op=0;
    		cp.n[1]=1;
    		while(this->cmp(b)!=-1) b.ly(),cp.ly();
    		while(cp.n[1]||cp.n[0]>1)
    		{
    			if(this->cmp(b)!=-1)
    			{
    				c.cpy(*this-b);
    				this->cpy(c);
    				c.cpy(lt+cp);
    				lt.cpy(c);
    			}
    			b.ry();
    			cp.ry();
    		}
    		lt.out();
    		lt.cpy(*this);
    		this->cpy(d);
    		return lt;
    	}
    
    因为这里会用到减法,所以仍然由d来存储this,这里又定义了cp与lt,我们先往后看,然后是符号处理,如果this比b小,那b和ly就一直乘二,如果cp1里面有值,或者cp0大于1,则继续循环,如果this比b大,则c等于this减b,然后把c复制到this里,相当于this-=b;
    下面同样,lt+=cp;然后b和cp各除以2;
    这一切结束后,lt里面放的是商,*this里面是余数。剩下的部分依个人情况而定。
    那么这么做的原理是什么?我给大家讲讲原理,并模拟一下我们知道,
    二的倍数和1可以组成任何数,
    这是由于在二进制下,1是第一位;2是第二位,4是第三位......
    例如:
                  43210
                  11001
    (25)10=(11001)2=2^4+2^3+2^0=16+8+1=25
    
    N=n*a+b;
    N-n*a=b;
    
    127=3*42+1;
    127-3*42=1;
    
    b=3*2*2*2*2*2*2=192;
    cp=2*2*2*2*2*2=(64)10=(1000000)2
    
    127<196
    b>>=1,cp>>=1 
    b=96 cp=(100000)2
    
    127>96
    N-=b=31
    b>>=1,cp>>=1 
    b=48 cp=(10000)2  lt=(100000)2
    
    31<48
    b>>=1,cp>>=1 
    b=24 cp=(1000)2
    
    31>24
    N-=b=7
    b>>=1,cp>>=1 
    b=12 cp=(100)2  lt=(101000)2
    
    7<12
    b>>=1,cp>>=1 
    b=6 cp=(10)2    
    
    7>6
    N-=b=1
    b>>=1,cp>>=1 
    b=3 cp=(1)2    lt=(101010)2
    
    1<3
    b>>=1,cp>>=1 
    b=1 cp=(0)2
    end
    
    lt=(101010)2=(42)10
    
    所以,cp其实模拟的是商的二进制的每一位!看看哪些2的倍数加起来是a,最后剩下的就是余数。

    ()2指的是2进制下的数,()10同理

    最后附上完整代码
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<sstream>
    #include<queue>
    #include<map>
    #include<vector>
    #include<set>
    #include<deque>
    #define dd double
    #define ll long long
    #define N 10010
    using namespace std;
    
    struct bignum{
    	
    	ll n[N>>2];
        bool op=0;//1 +,0 -
        const ll mod=1e8;
    	
    	void out()
    	{
            if(!op&&(n[1]||n[0]!=1)) cout<<"-",op=0;
            printf("%lld",n[n[0]]);
            for(int i=n[0]-1;i>0;i--) printf("%08lld",n[i]);
            cout<<endl;
        }//over
        
        void ry()
        {
        	for(int i=n[0];i;--i)
        	{
        		if((n[i]&1)&&i>1) n[i-1]+=mod;
        		n[i]>>=1;
    		}
    		if(!n[n[0]]&&n[0]>1) --n[0];
    	}
        
        void ly()
        {
        	++n[0];
        	for(int i=1;i<=n[0];i++)
        	{
        		n[i]<<=1;
        		if(n[i-1]>=mod) n[i-1]-=mod,++n[i];
    		}
    		if(!n[n[0]]&&n[0]>1) --n[0];
    	}
        
        void cpy(bignum a)
        {
        	for(int i=a.n[0];i>n[0];i--) n[i]=0;
        	for(int i=0;i<=a.n[0];i++) n[i]=a.n[i];
        	op=a.op;
    	}
        
        int cmp(bignum a)
        {
        	if(n[0]>a.n[0]) return 1;
        	if(n[0]<a.n[0]) return -1;
        	for(int i=n[0];i;i--)
        	{
        		if(n[i]>a.n[i]) return 1;
        		if(n[i]<a.n[i]) return -1;
    		}
    		return 0;
    	}
        
        void init()
        {
        	string ss;
        	cin>>ss;
        	if(ss[0]=='-') ss[0]='0',op=0;
        	int len=ss.length();
        	for(int i=len-1;i>=0;i-=8)
        	{
        		ll pw=1;
        		for(int j=i;j>i-8&&j>=0;j--)
        		{
        			n[n[0]]+=(ss[j]^48)*pw;
        			pw=(pw<<3)+(pw<<1);
    			}
    			++n[0];
    		}
    		n[0]--;
    	}
    	
    	void sp(bignum& b)
    	{
    		bignum c;
    		c.cpy(*this);
    		this->cpy(b);
    		b.cpy(c);
    	}
    	
    	bignum ()
    	{
    		memset(n,0,sizeof(n));
    		op=1;
    		n[0]=1;
    	}
    	
    	bignum operator + (bignum b)// a>0 b>0
    	{
    		bignum c;
    		if(op==b.op) c.op=op;
    		else
    		{
    			if(op==1)
    			{
    				b.op=1;
    				return *this-b;
    			}
    			else
    			{
    				op=1;
    				return b-*this;
    			}
    		}
    		c.n[0]=max(n[0],b.n[0])+1;
    		for(int i=1;i<=c.n[0];i++)
    		{
    			c.n[i]+=n[i]+b.n[i];
    			if(c.n[i]>=mod) c.n[i]-=mod,c.n[i+1]++;
    		}
    		while(!c.n[c.n[0]]&&c.n[0]>1) c.n[0]--;
    		return c;
    	}
    	
    	bignum operator - (bignum b) //a>b>0
    	{
    		bignum c,d;
    		d.cpy(*this);
    		if(op!=b.op)
    		{
    			b.op^=1;
    			return *this+b;
    		}
    		else c.op=op;
    		if(this->cmp(b)==-1)
    		{
    			this->sp(b);
    			c.op^=1;
    		}
    		c.n[0]=max(n[0],b.n[0]);
    		for(int i=1;i<=c.n[0];i++)
    		{
    			c.n[i]+=n[i]-b.n[i];
    			if(c.n[i]<0) c.n[i]+=mod,c.n[i+1]--;
    		}
    		while(!c.n[c.n[0]]&&c.n[0]>1) c.n[0]--;
    		this->cpy(d);
    		return c;
    	}
    	
    	bignum operator * (bignum b)
    	{
    		bignum c;
    		c.n[0]=n[0]+b.n[0];
    		for(int i=1;i<=n[0];i++)
    		{
    			for(int j=1;j<=b.n[0];j++)
    			{
    				c.n[i+j-1]+=n[i]*b.n[j];
    				if(c.n[i+j-1]>=mod) c.n[i+j]+=c.n[i+j-1]/mod,c.n[i+j-1]%=mod;
    			}
    		}
    		while(!c.n[c.n[0]]&&c.n[0]>1) c.n[0]--;
    		if(op!=b.op) c.op=0;
    		return c;
    	}
    	
    	bignum operator / (bignum b)
    	{
    		bignum cp,lt,c,d;
    		d.cpy(*this);
    		if(op!=b.op) lt.op=0;
    		cp.n[1]=1;
    		while(this->cmp(b)!=-1) b.ly(),cp.ly();
    		while(cp.n[1]||cp.n[0]>1)
    		{
    			if(this->cmp(b)!=-1)
    			{
    				c.cpy(*this-b);
    				this->cpy(c);
    				c.cpy(lt+cp);
    				lt.cpy(c);
    			}
    			b.ry();
    			cp.ry();
    		}
    		lt.out();
    		lt.cpy(*this);
    		this->cpy(d);
    		return lt;
    	}
    	
    };
    
    int main()
    {
    	bignum a,b,c;
    	a.init();
    	b.init();
    	cout<<endl;
    	cout<<endl;
    	c.cpy(a+b);
    	c.out();
    	c.cpy(a-b);
    	c.out();
    	c.cpy(a*b);
    	c.out();
    	c.cpy(a/b);
    	c.out();
    }
    
    这个代码有一个漏洞,如果存的数位数很大,大约有2000位这么大,会出现在输入时输入不完的问题,大概是到了string或cin的极限了吧,看看以后有机会改一下。
  • 相关阅读:
    终于把老板的项目搞完了---最后一步项目部署
    linux rz/sz 拖动文件上传
    layui之table.render使用(含后台详细代码实现)
    layui upload 后台获取不到值
    Layui upload动态传参,后台接收不到,解决方法
    hibernate 多条件查询,查询部分字段等操作
    IDEA自动生成序列化ID
    MySQL范围查询(日期)
    安全随机数!Java 随机数 Random 与 SecureRandom
    java poi 写excel到指定目录
  • 原文地址:https://www.cnblogs.com/TianMeng-hyl/p/12860367.html
Copyright © 2020-2023  润新知