”高精度计算(加减法乘法)“

Posted by 许大仙 on July 28, 2017

一、前言

高精度运算,是指参与运算的数(加数,减数,因子……)范围大大超出了标准数据类型(整型,实型)能表示的范围的运算。例如,求两个200位的数的和。这时,就要用到高精度算法了 高精度使用数组来存储整数,模拟手算进行四则运算

由于计算机运算是有模运算,数据范围的表示有一定限制,如整型int(C++中int 与long相同)表达范围是(-2^31~2^31-1),unsigned long(无符号整数)是(0~2^32-1),都约为几十亿.如果采用实数型,则能保存最大的double只能提供15~16位的有效数字,即只能精确表达数百万亿的数.因此,在计算位数超过十几位的数时,不能采用现有类型,只能自己编程计算.

二、高精度计算通用方法:

高精度计算时一般用一个数组来存储一个数,数组的一个元素对应于数的一位 (当然,在以后的学习中为了加快计算速度,也可用数组的一个元素表示数的多位数字,暂时不讲),表示时,由于数计算时可能要进位,因此为了方便,将数由低位到高位依次存在数组下标对应由低到高位置上,另外,我们申请数组大小时,一般考虑了最大的情况,在很多情况下,表示有富余,即高位有很多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';//将字符转为数字并倒序存储.(s的末尾记录在a的小下标处)
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;
}

3.直接初始化(用a[]存储)

初始化为0: memset(a,0,sizeof(a));

初始化为1: memset(a,0,sizeof(a));a[0]=1;a[1]=1;

以下程序都只写函数,不写完整程序,所有高精度数存储都满足上述约定。

四.高精度数比较

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;
}

ps:由于开数组的时候,考虑到结果的最大可能,所以前面几位都是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。大数b-小数a,返回负号
     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;
} # 八、高精度乘法2(高精度乘以高精度、要求用尽可能少的存储单元)    算法:用数组保存两个高精度数,然后逐位相乘,注意考虑进位和总位数。  
源程序如下:   
#include  <stdio.h>   
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]);          
}       

ps:2的幂可以转化成左移运算 i«m——-即:i=i*2^m

其余关于高精度之间的除法,乘法,和阶层运算,可以根据上面的方法写出。 参考详见:高精度算法

九、拓展:斯特林公式

斯特林公式是用于n极大的时候,近似模拟n!的值的数学公式。 它有个很大的优点就是,n越大,模拟的越好。尤其当n很大的时候,计算量大,复杂的时候,就显示出优点。

Stirling公式的意义在于:当n足够大时,n!计算起来十分困难,虽然有很多关于n!的等式,但并不能很好地对阶乘结果进行估计,尤其是n很大之后,误差将会非常大。但利用Stirling公式可以将阶乘转化成幂函数,使得阶乘的结果得以更好的估计。而且n越大,估计得越准确。

公式:stiring公式

上述公式满足:stiring近似公式

要计算n!的位数,可以利用配套的位数公式N!的位数=lgN!+1;

当n!=1的时候,所有位数都可以符合以下下面的公式: stiring公式计算n!位数