大整数的存储
我们可以定义一个结构体来存储:
struct bigNum{
int d[1000]; //存储大整数
int len; //大整数的长度
bigNum()
{
memset(d, 0, sizeof(d));
len = 0;
}
};
在我们输入大数时,一般先用字符串读入,然后再把字符串另存至bigNum结构体。由于使用string数组读入时,整数的高位会变成数组的低位,而整数的低位会变成数组的高位,因此需要对其进行处理:
bigNum change(string str)
{
bigNum a;
a.len = str.length(); //bigNum的长度就是字符串的长度
for(int i = 0;i<a.len;i++)
{
a.d[i] = str[a.len-i-1]-'0'; //逆序赋值
}
return a;
}
如果要比较两个大数也非常简单:先判断两个大数的len,如果不相等,则以长的为大;如果相等,则从高位到低位进行比较,直到出现某位不等,就可以判断两个数的大小。代码如下:
int compare(bigNum a,bigNum b) //比较a,b的大小,返回1,0,-1
{
if(a.len>b.len) return 1;
else if(a.len<b.len) return -1;
else
{
for(int i = a.len-1;i>=0;i--) //从高往低比较
{
if(a.d[i]>b.d[i]) return 1;
else if(a.d[i]<b.d[i]) return -1;
}
return 0; //两位数相等
}
}
有了以上对大数存储的基础,我们将进入大数的运算。
大整数的四则运算
1. 大整数加法
对其中一位进行加法的步骤:将该位上的两个数字与进位相加,得到的结果取个位数作为改为的结果,取十位数作为新的进位。
bigNum add(bigNum a,bigNum b)
{
bigNum c;
int carry = 0; //carry是进位
for(int i = 0;i<a.len||i<b.len;i++)
{
int temp = a.d[i]+b.d[i]+carry;
c.d[c.len++] = temp%10; //个位数为该位的结果
carry = temp/10; //十位数为新的进位
}
if(carry != 0) //如果最后进位不为0,则直接赋给最高位
{
c.d[c.len++] = carry;
}
return c;
}
注意,这样的写法的条件是两个对象都是非负的。如果有 一方是负的,可以在转换到数组这一步时去掉其负号,然后进行高精度减法;如果是两个负数,就都减去负号后用高精度加法,最后再吧负号加回去即可。
2. 大整数减法
步骤:对某一步,比较被减位和减位,如果不够减,则令被减位的高位减1、被减位加10再进行减法;如果够减,则直接减。最后一步要注意减法后高位可能有多余0,要去除它们,但也要保证结果至少有一位数。
bigNum sub(bigNum a,bigNum b)
{
bigNum c;
for(int i = 0;i<a.len||i<b.len;i++)
{
if(a.d[i]<b.d[i]) //如果不够减
{
a.d[i+1]--; //向高位借位
a.d[i]+=10; //当前位+10
}
c.d[c.len++] = a.d[i]-b.d[i];
}
while(c.len - 1>= 1&&c.d[c.len -1 ] == 0) //去除高位0
c.len--;
return c;
}
最后需要注意的是,使用sub函数前需要比较两个数的大小,如果被减数小于减数,需要交换两个变量,然后输出负号,再使用sub函数。
3. 高精度与低精度乘法
对于某一步的步骤:取bigNum的某位与int型整体相乘再与进位相加,所得结果的个位作为该位的结果,高位部分作为新的进位。
bigNum multi(bigNum a,int b)
{
bigNum c;
int carry = 0; //进位
for(int i = 0;i<a.len;i++)
{
int temp = a.d[i] * b + carry;
c.d[c.len++] = temp%10; //个位作为该位的结果
carry = temp/10; //高位部分作为新的进位
}
while(carry != 0)
{
c.d[c.len++] = carry%10;
carry/=10;
}
return c;
}
4. 高精度与低精度除法
对于某一步的步骤:上一步的余数乘上10加上该步的位,得到该步临时的被除数,将其与除数比较;如果不够除,则该位的商为0;如果够除,则该位的商即为对应的商,余数即为对应的余数。最后一步要注意除法后高位可能会有多余的0,要去除它们,但也要保证结果至少有一位数。
bigNum divide(bigNum a,int b,int& r) //r为余数,初始时r = 0
{
bigNum c;
c.len = a.len; //被除数的每一位和商的每一位是一一对应的,因此先令其长度相等
for(int i = a.len-1;i>=0;i--)
{
r = r*10+a.d[i];
if(r<b) //如果不够除
c.d[i] = 0;
else //够除
{
c.d[i] = r/b; //商
r = r%b;
}
}
while(c.len - 1>=1&&c.d[c.len-1]==0)
{
c.len--;
}
return c;
}
当然注意以上的乘法和除法是只能用于其中一个数不需要使用高精度,如果要提高算法的普适性,将需要使用以下的方法
5. 高精度与高精度乘法
步骤:即一个数的第i 位和另一个数的第j 位相乘所得的数,一定是要累加到结果的第i+j 位上。这里i, j 都是从右往左,从0 开始数。然后再处理进位,当前的值加上进位的值再看本位数字是否又有进位。
bigNum multi(bigNum a, bigNum b)
{
bigNum c;
for (int i = 0; i < a.len; i++)
{
for (int j = 0; j < b.len; j++)
{
c.d[i + j] += (a.d[i] * b.d[j]); //先乘起来,后面统一进行进位
}
}
int i = 0;
while(c.d[i]!=0)
{
if(c.d[i]>=10) //若>=10
{
c.d[i+1]=c.d[i+1]+c.d[i]/10; //将十位上数字进位
c.d[i] = c.d[i] % 10; //将个位上的数字留下
}
i++;
}
c.len = i;
return c;
}
6. 高精度与高精度除法
这个除法和手写模拟有点不一样,其实可以利用我们前面已经写好了的函数来模拟。
算法基本原理:就是被除数能减去除数多少次,减的次数就是商,减完剩下的部分就是余数。
int compare(bigNum a,bigNum b) //比较a,b的大小,返回1,0,-1
{
if(a.len>b.len) return 1;
else if(a.len<b.len) return -1;
else
{
for(int i = a.len-1;i>=0;i--) //从高往低比较
{
if(a.d[i]>b.d[i]) return 1;
else if(a.d[i]<b.d[i]) return -1;
}
return 0; //两位数相等
}
}
bigNum sub(bigNum a,bigNum b)
{
bigNum c;
for(int i = 0;i<a.len||i<b.len;i++)
{
if(a.d[i]<b.d[i]) //如果不够减
{
a.d[i+1]--; //向高位借位
a.d[i]+=10; //当前位+10
}
c.d[c.len++] = a.d[i]-b.d[i];
}
while(c.len - 1>= 1&&c.d[c.len -1 ] == 0) //去除高位0
c.len--;
return c;
}
bigNum divide(bigNum a, bigNum b) //a是被除数,b是除数
{
int sum = 0;
while (compare(a, b) >= 0) //当a>=b就一直继续
{
a = sub(a, b);
sum++;
}
return a;
}
当然这个方法有两个缺点:
- 慢
- 当商大于21.5亿时,结果会溢出
不过目前来说做题的话,这个方法已经够了,以后再继续补充。