先介绍一种高精度的优化方法,事实上这种优化没有改变算法的时间复杂度,也就是没有改变他的增长曲线但却使增长变慢了。然后再介绍一下减法。
现在常用的高精度计算方法是把字符串中每个字符转化为一个数倒序存储在另一个数组中,这样做既浪费空间,又没有时效。因为最简单的整型数char最大可以存储255,用它存储个位数浪费了很多空间。而且逐位计算也花费很多时间。不如让一个数存储尽可能多的位,这样对一个数组元素的计算只需要一次,那么总的循环次数相应的缩短为几分之一,比如让char存储两位数(它不可以存储三位数因为最大的三位数999超出了它的范围)那么计算12+34的运算也相应的成为一次普通的计算,而不需要向原始的方法那样循环两次。我在下面这个程序中用了long,因为long可以存储最多10位的数,因此可以用它存储任意9 位数,处理的时候注意如果它的位数不是九的倍数那么会有多余,把多余的放到最高位。这样做可以把循环次数缩小为1/9。这相当于是一个 1000000000进制的计算,用10进制显示它的每一位,发现这样一个1000000000进制的数它的显示与10进制完全一样的。但要注意的是,它的每一位输出必须为9位。如果10进制下小于九位则要在前面补零。比如某一位是100,在1000000000进制的数中要输出为000000100,c 语言有该格式的应用,pascal中MS要计算前补零了,如果是最高位的话当然可以把前面的零省掉了。
下面是该算法的程序
#include<stdio.h>
#include<string.h>
int main()
{
char a1[100000],b1[100000];
long a[10000]={0},b[10000]={0},c[10000]={0},sa,sb,la,lb,lena,lenb,p,i,j,k,x=0,lenc,l;
gets(a1); //读入
gets(b1);
la=strlen(a1); //算长度
lb=strlen(b1);
sa=la%9; //计算每九位划分后的剩余位数
sb=lb%9;
lena=la/9; lenb=lb/9;
k=1;
for(p=sa-1;p>=0;p--)
{a[lena]+=(a1[p]-48)*k;k*=10;} //处理第一个加数,每九位划分后剩余的位数转化为一个数
p=sa;
for(i=lena-1;i>=0;i--) //每九个字符转换为一个九位数存储在a[i]中
{
k=100000000;
for(j=1;j<=9;j++)
{
a[i]+=(a1[p]-48)*k;
k/=10;
p+=1;
}
}
k=1;
for(p=sb-1;p>=0;p--){b[lenb]+=(b1[p]-48)*k;k*=10;} //处理第二个加数,同上
p=sb;
for(i=lenb-1;i>=0;i--)
{
k=100000000;
for(j=1;j<=9;j++)
{
b[i]+=(b1[p]-48)*k;
k/=10;
p+=1;
}
}
i=0;
while ((i<=lena)||(i<=lenb)) //计算
{
c[i]=a[i]+b[i]+x;
x=c[i]/1000000000;
c[i]%=1000000000;
i++;
}
if (x!=0){lenc=i;c[lenc]=x;} //计算结果有没有增位
else lenc=i-1;
for(i=lenc;i>=0;i--)
if (i!=lenc)printf("%09ld",c[i]);else printf("%ld",c[i]); //出开头按九位数输出
return 0;
}
以下是一个pascal的高精度的乘法,原理同上,我只做了100进制,读者可以自己扩展。
program acb;
var
la,lb,lc,x,i,j,l:integer;
s,st:ansistring;
a,b,c:array[1..100000]of byte;
f1,f2:text;
begin
assign(f1,'qlwy.in');
reset(f1);
assign(f2,'qlwy.out');
rewrite(f2);
readln(f1,s);
la:=length(s); l:=la; i:=1;
while l+2>0 do
begin
if l-1>0 then
st:=s[l-1]+s[l] else st:=s[l];
val(st,a[i]);
i:=i+1; l:=l-2;
end;
if la mod 2=0 then la:=la div 2 else la:=la div 2+1;
for i:=la downto 1 do writeln(f2,a[i]);
readln(f1,s);
lb:=length(s); l:=lb; i:=1;
while l+2>0 do
begin
if l-1>0 then
st:=s[l-1]+s[l] else st:=s[l];
val(st,b[i]);
i:=i+1; l:=l-2;
end;
if lb mod 2=0 then lb:=lb div 2 else lb:=lb div 2+1;
for i:=lb downto 1 do writeln(f2,b[i]);
for i:=1 to la do
begin
x:=0;
for j:=1 to lb do
begin
x:=a[i]*b[j]+x div 100+c[i+j-1];
c[i+j-1]:=x mod 100;
end;
c[i+j]:=x div 100;
end;
lc:=i+j;
while (c[lc]=0)and(lc>1)do dec(lc);
for i:=lc downto 1 do
begin
if c[i]<10 then write(f2,0);{补零}
write(f2,c[i]);
end;
close(f1);
close(f2);
end.
减法的关键在于符号和借位的问题,判断符号只要判断被减数与减数的大小即可,可以从位数与首位大小来判断。借位的问题其实不需要作任何判断,不论如何都向前借一位,然后再像加法进位一样进上去就行了。
#include <stdio.h>
char a1[1000],b1[1000],d,a[1000],b[1000],c[1000];
int la,lb,lc,i,x;
void t1()
{
d=0; //符号为正,将数据倒置存放,使个位在数组0下标
for(i=0;i<la;i++)a[i]=a1[la-i-1]-'0';
for(i=0;i<lb;i++)b[i]=b1[lb-i-1]-'0';
}
void t2()
{
d='-'; //符号为负,并交换倒置减数被减数
for(i=0;i<la;i++)b[i]=a1[la-i-1]-'0';
for(i=0;i<lb;i++)a[i]=b1[lb-i-1]-'0';
}
int main()
{
gets(a1); //读入
gets(b1);
la=strlen(a1); //算长度
lb=strlen(b1);
memset(b,0,sizeof(b)); //数组的初始化
memset(a,0,sizeof(a));
memset(c,0,sizeof(c));
if(la>lb)
t1();
if(la==lb)
if(strcmp(a1,b1)>=0)t1();
else t2();
if(la<lb)t2();
i=0;x=0; //计算
while(i<la||i<lb)
{
x=a[i]+10-b[i]+x;
c[i]=x%10;
a[i+1]-=1;
x=x/10;
i++;
}
while(c[i]==0&&i>0)i--; //计算结果的实际长度,去前零
lc=i;
if(d)printf("%c",d); //符号
for(i=lc;i>=0;i--)printf("%d",c[i]);
printf("/n");
return 0;
}
除法的计算与前面的算法有所不同,由于除法不是一位一位的进行运算,所以直接用单精度的除法操作不能得到一个高精度的除法运算,要用减法一次次的数出商的每一位。因此比较繁琐,为方便起见,在除法前要先写一个减法的函数以方便使用和简化结构。也可以用前面的优化措施,这道程序是每4位存储在一个变量中的,但这样或许会造成一些不好,因为如果是逐位的作减法得到商12 只要三次减法即可,如果压缩却要进行12次减法,但在大数据中却节省了移位这个复杂的过程的运算次数,因为在除法笔算的算法中,余数要拿下被除数的一位加在末尾做下一次除法,这个过程在程序中是很复杂的,存储余数的d数组末尾在0端,没有空余的位置把被除数加下来,所以要有一个向左移位的过程,这个过程执行的效率与这个数组的有效位数密切相关,因为要从最高位开始全部向左移位,压缩后的数据有效位成了原来的1/4,而且做这种移位的次数也成了原来的1 /4,减法的执行效率也有一定提高,这样,对于大的数据来说是抛砖引玉之策 了。
#include <stdio.h>
#include<string.h>
void convert(char*a, int*b) { //转置
int la, i, lm, k, j;
la = strlen(a);
lm = la % 4;
b[0] = la / 4; //0下标为有效位的长度
if (lm > 0) {
b[0]++;
b[b[0]] = 0;
k = 1;
for (i = lm - 1; i >= 0; i--) {
b[b[0]] += (a[i] - '0') * k;
k *= 10;
}
}
j = 1;
k = 1;
for (i = la - 1; i >= lm; i--) {
if (k == 10000) {
k = 1;
j++;
}
b[j] += (a[i] - '0') * k;
k *= 10;
}
}
void substration(int *a, int *b, int*c) { //减法
int i, x;
i = 1;
x = 0;
while (i <= a[0] || i <= b[0]) {
a[i + 1]--;
c[i] = 10000 + x + a[i] - b[i];
x = c[i] / 10000;
c[i] %= 10000;
i++;
}
c[0] = i - 1;
while (c[c[0]] == 0 && c[0] > 1) //去掉最高位的0
c[0]--;
}
int compare(int*a, int*b) //高精度比较
{
int i = a[0];
int s = 0;
if (a[0] != b[0])return (a[0] - b[0]); //长度不同的,长的数为大
else {
while (s == 0 && i > 0) { //不同按字典序比较
s = a[i] - b[i];
i--;
}
return s;
}
}
void mult10000(int*a) { //向左移位一次
int i;
for (i = a[0]; i >= 1; i--)
a[i + 1] = a[i];
a[1] = 0;
a[0] += 1;
while (a[a[0]] == 0 && a[0] > 1) //去掉最高位的0
a[0] -= 1;
}
void division(int *a, int *b, int *c, int *d) { //a被除数,b除数,c商,d余数
int i;
for (i = 0; i < 800; i++)c[i] = 0; //初始化数组c
d[0] = 1;
d[1] = 0; //初始化数组d
i = a[0];
while (i > 0) {
mult10000(d);
d[1] = a[i];
while (compare(d, b) >= 0) {
substration(d, b, d);
c[i]++;
if (c[0] == 0)c[0] = i; //最高位
}
i--;
}
if (c[0] == 0)c[0] = 1; //商为0
}
void output(int*a) { //输出
int i;
printf("%d", a[a[0]]);
for (i = a[0] - 1; i >= 1; i--) {
printf("%04d", a[i]);
}
}
int main() {
int a[800], b[800], c[800], d[800];
char a1[805], b1[805];
while (scanf("%s%s", a1, b1) != EOF) {
memset(a, 0, sizeof (a));
memset(b, 0, sizeof (b));
convert(a1, a); //转置
convert(b1, b);
if (b[0] != 1 || b[1] > 0) { //除数不等于0 !(b[0]==1&&b[1]==0)
division(a, b, c, d);
output(c);
if (d[0] != 1 || d[1] > 0) { //d不等于0. !(d[0]==1&&d[1]==0)
printf(" ");
output(d);
}
printf("/n");
}
}
return 0;
}