由于计算机在计算位数超过十几位的数时,不能采用现有类型,只能自己编程计算。
高精度计算通用方法:
高精度计算时一般用一个数组来存储一个数,数组的一个元素对应于数的一位(当然,在以后的学习中为了加快计算速度,也可用数组的一个元素表示数的多位数字,暂时不讲),表示时,由于数计算时可能要进位,因此为了方便,将数由低位到高位依次存在数组下标对应由低到高位置上,另外,我们申请数组大小时,一般考虑了最大的情况,在很多情况下,表示有富余,即高位有很多0,可能造成无效的运算和判断,因此,我们一般将数组的第0个下标对应位置来存储该数的位数。如数:3485(三千四百八十五),表达在数组a[10]上情况是:
下标 0 1 2 3 4 5 6 7 8 9
内容 4 5 8 4 3 0 0 0 0 0
说明:位数 个位 十位 百位 千位
具体在计算加减乘除时方法就是小学时采用的列竖式方法。
注:高精度计算时一般用正数,对于负数,通过处理符号位的修正。
一.高精度数的存储
1.如对数采用的字符串输入
#include <iostream>
#include <cstring>
using namespace std;
const int N=100;//最多100位
int main()
{
int a[N+1],i;
string s1;
cin>>s1;//数s1
memset(a,0,sizeof(a)); //数组清0
a[0]=s1.length(); //位数
for(i=1;i<=a[0];i++) a[i]=s1[a[0]-i]-'0';//将字符转为数字并倒序存储.
return 0;
}
2.直接读入
#include <iostream>
using namespace std;
const int N=100;//最多100位
int main()
{
int a[N+1],i,s,key;
cin>>key;//数key
memset(a,0,sizeof(a)); //数组清0
i=0;//第0位
while(key) //当key大于0
{
a[++i]=key%10;//取第i位的数
key=key/10;
}
a[0]=i; //共i位数
return 0;
}
以下程序都只写函数,不写完整程序,所有高精度数存储都满足上述约定。
二.高精度数比较
int compare(int a[],int b[]) //比较a和b的大小关系,若a>b则为1,a<b则为-1,a=b则为0
{
int i;
if (a[0]>b[0]) return 1;//a的位数大于b则a比b大
if (a[0]<b[0]) return -1;//a的位数小于b则a比b小
for(i=a[0];i>0;i--) //从高位到低位比较
{
if (a[i]>b[i]) return 1;
if (a[i]<b[i]) return -1;
}
return 0;//各位都相等则两数相等。
}
三、高精度加法
int plus(int a[],int b[]) //计算a=a+b
{
int i,k;
k=a[0]>b[0]?a[0]:b[0]; //k是a和b中位数最大的一个的位数
for(i=1;i<=k;i++)
{
a[i+1]+=(a[i]+b[i])/10; //若有进位,则先进位
a[i]=(a[i]+b[i])%10;
} //计算当前位数字,注意:这条语句与上一条不能交换。
if(a[k+1]>0) a[0]=k+1; //修正新的a的位数(a+b最多只能的一个进位)
else a[0]=k;
return 0;
}
四、高精度减法
int gminus(int a[],int b[]);//计算a=a-b,返加符号位0:正数 1:负数
{
int flag,i;
flag=compare(a,b); //调用比较函数判断大小
if (falg==0)//相等
{
memset(a,0,sizeof(a));return 0;
} //若a=b,则a=0,也可在return前加一句a[0]=1,表示是 1位数0
if(flag==1) //大于
{
for(i=1;i<=a[0];i++)
{
if(a[i]<b[i]){
a[i+1]--;a[i]+=10;
} //若不够减则向上借一位
a[i]=a[i]-b[i];
}
while(a[a[0]]==0) a[0]--; //修正a的位数
return 0;
}
if (flag==-1)//小于 则用a=b-a,返回-1
{
for(i=1;i<=b[0];i++){
if(b[i]<a[i]){ b[i+1]--;b[i]+=10;} //若不够减则向上借一位
a[i]=b[i]-a[i];
}
a[0]=b[0];
while(a[a[0]]==0) a[0]--; //修正a的位数
return -1;
}
}
五、高精度乘法1(高精度乘单精度数,单精度数是指通常的整型数)
int multi1(int a[],long key) //a=a*key,key是单精度数
{
int i,k;
if (key==0){memset(a,0,sizeof(a));a[0]=1;return 0;} //单独处理key=0
for(i=1;i<=a[0];i++)a[i]=a[i]*key;//先每位乘起来
for(i=1;i<=a[0];i++){a[i+1]+=a[i]/10;a[i]%=10;} //进位
//注意上一语句退出时i=a[0]+1
while(a[i]>0) {a[i+1]=a[i]/10;a[i]=a[i]%10;i++;a[0]++;}
//继续处理超过原a[0]位数的进位,修正a的位数
return 0;
}
六、 高精度除以低精度;
算法:按照从高位到低位的顺序,逐位相除。在除到第j位时,该位在接受了来自第j+1位的余数后与除数相除,如果最高位为零,则商的长度减一。源程序如下:
#include <stdio.h>
#define N 500
main()
{
int a[N] = {0}, c[N] = {0};
char a1[N];
printf("Input 除数:");
printf("Input 被除数:");
scanf("%s", a1);
k = strlen(a1);
for(i = 0; i < k; i++) a[i] = a1[k - i - 1] - '0';
d = 0;
for(i = k - 1; i >= 0 ; i--)
{
d = d * 10 + a[i];
c[i] = d / b;
d = d % b;
}
while(c[k - 1] == 0 && k > 1) k--;
printf("商=");
printf("\n余数=%d", d);
}
七、高精度乘以高精度(要求用尽可能少的存储单元);
算法:用数组保存两个高精度数,然后逐位相乘,注意考虑进位和总位数。源程序如下:
#include <cstdio>
main()
{
int a[240] = {0}, b[240] = {0}, c[480] = {0};
int i, j, ka, kb, k;
char a1[240], b1[240];
gets(a1);
ka = strlen(a1);
gets(b1);
kb = strlen(b1);
k = ka + kb;
for(i = 0; i < ka; i++) a[i] = a1[ka-i-1] - '0';
for(i = 0; i < kb; i++) b[i] = b1[kb-i-1] - '0';
for(i = 0; i < ka; i++)
for(j = 0; j < kb; j++)
{
c[i + j] = c[i + j] + a[i] * b[j];
c[i + j +1] = c[i + j +1] + c[i + j]/10;
c[i + j] = c[i + j] % 10;
}
if(!c[k]) k--;
for(i = k-1; i >= 0; i--) printf("%d", c[i]);
}
六、高精度除于高精度
基本的思想是反复做减法,看看从被除数里最多能减去多少个除数,商就是多少。一个一个减显然太慢,如何减得更快一些呢?
以7546除以23为例来看一下:
开始商为0。先减去23的100倍,就是2300,发现够减3次,余下646。于是商的值就增加300。
然后用646减去230,发现够减2次,余下186,于是商的值增加20。
最后用186减去23,够减8次,因此最终商就是328。
所以本题的核心是要写一个大整数的减法函数,然后反复调用该函数进行减法操作。计算除数的10倍、100倍的时候,不用做乘法,直接在除数后面补0即可。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include <iostream>
#define MaxLen 200
using namespace std;
//函数SubStract功能:
//用长度为len1的大整数p1减去长度为len2的大整数p2
// 结果存在p1中,返回值代表结果的长度
//不够减 返回-1 正好够 返回0
int SubStract( int *p1, int *p2, int len1, int len2 )
{
int i;
if( len1 < len2 )
return -1;
if( len1 == len2 )
{ //判断p1 > p2
for( i=len1-1; i>=0; i-- )
{
if( p1[i] > p2[i] ) //若大,则满足条件,可做减法
break;
else if( p1[i] < p2[i] ) //否则返回-1
return -1;
}
}
for( i=0; i<=len1-1; i++ ) //从低位开始做减法
{
p1[i] -= p2[i];
if( p1[i] < 0 ) //若p1<0,则需要借位
{
p1[i] += 10; //借1当10
p1[i+1]--; //高位减1
}
}
for( i=len1-1; i>=0; i-- ) //查找结果的最高位
if( p1[i] ) //最高位第一个不为0
return (i+1); //得到位数并返回
return 0; //两数相等的时候返回0
}
int main()
{
int n, k, i, j; //n:测试数据组数
int len1, len2; //大数位数
int nTimes; //两大数相差位数
int nTemp; //Subtract函数返回值
int num_a[MaxLen]; //被除数
int num_b[MaxLen]; //除数
int num_c[MaxLen]; //商
char str1[MaxLen + 1]; //读入的第一个大数
char str2[MaxLen + 1]; //读入的第二个大数
scanf("%d",&n);
while ( n-->0 )
{
scanf("%s", str1); //以字符串形式读入大数
scanf("%s", str2);
cout<<str1<<" "<<str2<<endl;
for ( i=0; i<MaxLen; i++ ) //初始化清零操作
{
num_a[i] = 0;
num_b[i] = 0;
num_c[i] = 0;
}
len1 = strlen(str1); //获得大数的位数
len2 = strlen(str2);
for( j=0, i=len1-1; i>=0; j++, i-- )
num_a[j] = str1[i] - '0'; //将字符串转换成对应的整数,颠倒存储
for( j=0, i=len2-1; i>=0; j++, i-- )
num_b[j] = str2[i] - '0';
if( len1 < len2 ) //如果被除数小于除数,结果为0
{
printf("0\n");
continue; //利用continue直接跳出本次循环。 进入下一组测试
}
nTimes = len1 - len2; //相差位数
for ( i=len1-1; i>=0; i-- ) //将除数扩大,使得除数和被除数位数相等
{
if ( i>=nTimes )
num_b[i] = num_b[i-nTimes];
else //低位置0
num_b[i] = 0;
}
len2 = len1;
for( j=0; j<=nTimes; j++ ) //重复调用,同时记录减成功的次数,即为商
{
while((nTemp = SubStract(num_a,num_b + j,len1,len2 - j)) >= 0)
{
len1 = nTemp; //结果长度
num_c[nTimes-j]++;//每成功减一次,将商的相应位加1
}
}
//输出结果
for( i=MaxLen-1; num_c[i]==0 && i>=0; i-- );//跳过高位0
if( i>=0 )
for( ; i>=0; i-- )
printf("%d", num_c[i]);
else
printf("0");
printf("\n");
}
return 0;
}