大数除法(超长整数运算除法器)详解
在大数运算中,比较难实现的应该是高精度/高精度的除法器。一、原理1.大数存储先说说大数在C语言程序中是怎么存储的。我们使用长度为N的int数组来存储无符号超长整数,其中数组每个元素存储的值上限为M。如下:#define M 10000//M进制,int数组每个元素取值上限#define N 5//数组长度int x[N]={0};因为int类型最多表示10位有效数字(最大值为2147483648)
在大数运算中,比较难实现的应该是高精度/高精度的除法器。
目录
一、原理
1.大数存储
先说说大数在C语言程序中是怎么存储的。我们使用长度为N的int数组来存储无符号超长整数,其中数组每个元素存储的值上限为M。如下:
#define M 10000 //M进制,int数组每个元素取值上限
#define N 5 //数组长度
int x[N]={0};
因为int类型最多表示10位有效数字(最大值为2147483648),当两个有效位数为5的int类型变量相乘时其结果可能溢出,为了便于乘法器的实现M取值最好小于10000
举个例子,当我们要用x存储一个无符号超长整数6543210435135314时,数组x的值是这样的:x[0]=5314,x[1]=3513,x[2]=2104,x[3]=6543,x[4]=0。
2.除法算法
除法操作数的关系如下:
被除数/除数=商......余数,其中被除数=商*除数+余数
首先除数不能为0;
当被除数为0时,商为0;
当被除数小于除数时,商为0,余数为被除数;
当被除数等于除数时,商为1,余数为0;
……
1)日常生活中,我们是这样算除法的:
2)但是在大数除法中,我们不能直接这么算。例如:对于1234 5678 9000 / 1 2345,计算机没法直接计算1234 5678 / 1 2345的结果。
3)在计算机中,减法的实现要比除法简单得多,因此我们考虑把除法转变成减法来计算。例如:对于7894/32,我们有:
通过被除数不断地减去除数,我们得到了余数22,而除数被减的次数就是商246。
4)现在我们已经知道了如何在计算机中把除法转变成减法来运算,但一个除数一个除数的减实在太慢了,那么有没有更快的算法呢?答案是肯定的,我们可以通过移位来加快“减”的速度,例如:
如图,7894=32*2*100+32*4*10+32*6*1+22,一共减去了2*100+4*10+6*1=246个32,所以7894/32的商为246。
二、具体代码解析
1.首先函数原型如下:
#define M 10000 //M进制,int数组每个元素取值上限
#define N 5 //数组长度
int div2(int n1[],int n2[],int quotient[]);
/* 大数除法:无符号高精度整数/无符号高精度整数,n1为被除数,n2为除数,quotient为商。函数正确执行时返回整数1 */
此外还涉及到这些函数:
int add(int num1[],int num2[],int sum[]);
/* 大数加法 */
int sub(int num1[],int num2[],int difference[]);
/* 大数减法 */
int mul(int num1[],int num2[],int product[]);
/* 大数乘法 */
int div(int num1[],int num2,int quotient[]);
/* 大数除法:无符号高精度整数/无符号低精度整数,其中num2>0且num2<=M */
int cmp(int num1[],int num2[]);
/* 大数比较,num1>num2时return1,小于时返回-1,等于时返回0 */
2.异常值处理
因为我们这里存储的是无符号超长整数,参与运算的操作数也应该是无符号,并且数组的每个元素应该小于M。所以有:
int div2(int n1[],int n2[],int quotient[]){
int i=0;
int num1[N]={0},num2[N]={0};
for(i=0;i<N;i++){
//异常值处理
if(n1[i]<0||n2[i]<0||n1[i]>=M||n2[i]>=M){
printf("div2():Operand are forbidden!\n");
return -2;
}
//初始化:
num1[i]=n1[i];
num2[i]=n2[i];
quotient[i]=0;
}
return 1;//运算正确
}
这里说明一下为什么函数不直接用n1、n2参与运算,这是因为这个函数div2传递的参数都是指针,而接下来的算法将会改变被除数的值。为防止函数结束后被除数的值被改变,所以新定义了和n1、n2值相等的两个局部变量num1、num2。
3.特殊值处理
包含以下特殊情况的处理: (1)首先除数不能为0;(2)当被除数为0时,商为0;(3)当被除数小于除数时,商为0;(4)当被除数等于除数时,商为1。
/*----特殊值处理----*/
//检查除数是否为0
for(i=N-1;i>=0&&(num2[i]==0);i--);//从高位开始寻找num2首个不为0的下标
if(i<0){
printf("除数不能为0\n");
return -1;
}
//被除数小于除数
if((flag=cmp(num1,num2))==-1){
quotient[0]=0;
return 1;
}
//被除数等于除数
else if(flag==0){
quotient[0]=1;
return 1;
}
/*---特殊值处理end---*/
//别忘了在前面声明flag!
4.处理完上述特殊情况,剩下的就是被除数大于除数的情况了,从这里开始就是算法的核心部分。
首先,为了加快减的速度,我们要尽可能地把除数左移。因为M取值10000,num2[i]有效位数为4,我们先4位(十进制位)4位(十进制位)的左移,直到除数num2大于被除数num1时停止。接下来把除数1位(十进制位)1位(十进制位)地右移,直到num1>num2时停止。这时,被除数num1刚好大于除数num2。
/*---计算除数最大移位---*/
if(num2[N-1]>0)
flag=0;//flag=0表示除数不能移位,因为最高位>0
for(counts=0;counts<N&&(flag>0);){
//num2左移4位:
for(i=N-1;i>0;i--)
num2[i]=num2[i-1];
num2[0]=0;
counts++;//左移次数+4,counts+1
flag=cmp(num1,num2);
}
counts=counts*4;
for(i=0;i<counts&&(cmp(num1,num2)<0);i++){
if(div(num2,10,num2)!=1) return -1;//如果除数num2大于被除数num1,右移1位
}
counts=counts-i;//此时counts为除数左移位数
/*--计算除数最大移位end--*/
//这里左移一个10进制位是用高精度整数/低精度整数除法div实现的
//counts为num2左移位数(十进制位),别忘了在前面声明!
5.计算被除数减去除数的次数
for(;counts>=0;counts--){
if(cmp(num1,num2)>=0){
//除数num2左移counts位时,num1最多能减去num2的次数times:
for(times=0;times<M&&(cmp(num1,num2)>=0);times++){
sub(num1,num2,num1);
}
//除数num2左移counts位时,被除数最多能减去除数t=times*(10^counts)次:
for(i=0;i<N;i++)
{t[i]=0;m[i]=0;}//初始化t、m
t[0]=times;
m[0]=10;
for(i=0;i<counts;i++){
if(mul(t,m,t)!=1)
return -1;
}
//“减”的次数:
if(add(quotient,t,quotient)!=1) return -1;
}
if(div(num2,10,num2)!=1) return -1;//除数右移1位
}
//别忘了在前面声明times、t、m!
6.完整的div2函数定义如下:
#define M 10000 //M进制,int数组每个元素取值上限
#define N 5 //数组长度
//函数声明:
int add(int num1[],int num2[],int sum[]);
int cmp(int num1[],int num2[]);
int sub(int num1[],int num2[],int difference[]);
int mul(int num1[],int num2[],int product[]);
int div(int num1[],int num2,int quotient[]);
/*
===无符号超长整数 除法===
quotient=n1/n2
高精度整数/高精度整数
*/
int div2(int n1[],int n2[],int quotient[]){
int i=0,flag=1,counts=0,times=0;
int t[N]={0},m[N]={0},num1[N]={0},num2[N]={0};
for(i=0;i<N;i++){
//异常值处理
if(n1[i]<0||n2[i]<0||n1[i]>=M||n2[i]>=M){
printf("div2():Operand are forbidden!\n");
return -2;
}
//初始化:
num1[i]=n1[i];
num2[i]=n2[i];
quotient[i]=0;
}
/*----特殊值处理----*/
//检查除数是否为0
for(i=N-1;i>=0&&(num2[i]==0);i--);//从高位开始寻找num2首个不为0的下标
if(i<0){
printf("除数不能为0\n");
return -1;
}
//被除数小于除数
if((flag=cmp(num1,num2))==-1){
quotient[0]=0;
return 1;
}
//被除数等于除数
else if(flag==0){
quotient[0]=1;
return 1;
}
/*---特殊值处理end---*/
/*---计算除数最大移位---*/
if(num2[N-1]>0)
flag=0;//flag=0表示除数不能移位,因为最高位>0
for(counts=0;counts<N&&(flag>0);){
//num2左移4位:
for(i=N-1;i>0;i--)
num2[i]=num2[i-1];
num2[0]=0;
counts++;//左移次数+4,counts+1
flag=cmp(num1,num2);
}
counts=counts*4;
for(i=0;i<counts&&(cmp(num1,num2)<0);i++){
if(div(num2,10,num2)!=1) return -1;//如果除数num2大于被除数num1,右移1位
}
counts=counts-i;//此时counts为除数左移位数
/*--计算除数最大移位end--*/
for(;counts>=0;counts--){
if(cmp(num1,num2)>=0){
//除数num2左移counts位时,num1最多能减去num2的次数times:
for(times=0;times<M&&(cmp(num1,num2)>=0);times++){
sub(num1,num2,num1);
}
//除数num2左移counts位时,被除数最多能减去除数t=times*(10^counts)次:
for(i=0;i<N;i++)
{t[i]=0;m[i]=0;}//初始化t、m
t[0]=times;
m[0]=10;
for(i=0;i<counts;i++){
if(mul(t,m,t)!=1)
return -1;
}
//“减”的次数:
if(add(quotient,t,quotient)!=1) return -1;
}
if(div(num2,10,num2)!=1) return -1;//除数右移1位
}
return 1;//运算正确
}
三、超长整数运算
如果需要大数运算的完整代码(包括输入输出、加减乘除函数),可以参考我的另一篇文章:超长整数运算——从斐波那契数列说起 的<无符号超长整数运算>部分。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)