posts - 297,  comments - 15,  trackbacks - 0
在计算机中,长整型(long int)变量的范围是 -2147483648 至 2147483647,因此若用长整型变量做乘法运算,乘积最多不能超过 10位数。即便用双精度型(double)变量,也仅能保证 16 位有效数字的精度。在某些需要更高精度的乘法运算的场合,需要用别的办法来实现乘法运算。 比较容易想到的是做多位数乘法时列竖式进行计算的方法,只要写出模拟这一过程的程序,就能实现任意大整数的乘法运算。经过查阅资料,找到一种更易于编程的方法,即“列表法”。 下面先介绍“列表法”: 例如当计算8765 x 234时,把乘数与被乘数照如下列出,见表1: 8 7 6 5 8765 2 16 14 12 10 2 3 24 21 18 15 3 4 32 28 24 20 4 表 1 表 2 把每个空格所在的行列的数字的乘积填入空格中,得表2。把表2中的数按图示斜线分组(横纵坐标和相等的数分为一组),把每组数的累加起来所得的和记在表格下方,见表 3: 16 14 12 10 24 21 18 15 32 28 24 20 16 38 65 56 39 20 表 3 最后把表格下方一行数作如下处理,见表4: 从最低位的 20 开始,保留个位数字“0”,把个位以外的数“2”进到前一位;把次低位的 39 加上低位进上来的 2 得 41,保留个位数字“1”,把“4”进到前一位;以此类推,直至最高位的 16,16 加上低位进上来的4得 20,保留“0”,把2进到最高位,得乘积答数 2051010。 16 38 65 56 39 20 2 16+4=20 38+7=45 65+6=71 56+4=60 39+2=41 留0进2 留5进4 留1进7 留0进6 留1进4 留0进2 2 0 5 1 0 1 0 表 4 根据以上思路就可以编写C 程序了,再经分析可得: 1、一个m 位的整数与一个 n 位的整数相乘,乘积为m+n-1 位或m+n 位。 2、程序中,用三个字符数组分别存储乘数、被乘数与乘积。由第 1 点分析知,存放乘积的字符数组 的长度应不小于存放乘数与被乘数的两个数组的长度之和。 3、可以把第二步“计算填表”与第三四步“累加进位”放在一起完成,可以节省存储表格 2所需的空间。 4、程序关键部分是两层循环,内层循环累计一组数的和,外层循环处理保留的数字与进位。 编写的程序如下: 程序 1 清单: /* multiply.c */ /* 11/20/2008 */ #define MAXLENGTH 1000 #include #include void compute(char *a, char *b, char *c); void main(void) { char a[MAXLENGTH], b[MAXLENGTH], c[MAXLENGTH * 2]; puts("Input multiplier :"); gets(a); puts("Input multiplicand :"); gets(b); compute(a, b, c); puts("Answer :"); puts(c); getchar(); } void compute(char *a, char *b, char *c) { int i, j, m, n; long sum, carry; m = strlen(a) - 1; n = strlen(b) - 1; for (i = m; i >= 0; i--) a[i] -= '0'; for (i = n; i >= 0; i--) b[i] -= '0'; c[m + n + 2] = '\0'; carry = 0; for (i = m + n; i >= 0; i--) /* i 为坐标和 */ { sum = carry; if ((j = i - m) < 0) j = 0; for ( ; j<=i && j<=n; j++) /* j 为纵坐标 */ sum += a[i-j] * b[j]; /* 累计一组数的和 */ c[i + 1] = sum % 10 + '0'; /* 算出保留的数字 */ carry = sum / 10; /* 算出进位 */ } if ((c[0] = carry+'0') == '0') /* if no carry, */ c[0] = '\040'; /* c[0] equals to space */ } 效率分析:用以上算法计算 m位整数乘以n 位整数,需要先进行 m x n次乘法运算,再进行约 m + n次加法运算和 m + n次取模运算(实为整数除法)。把这个程序稍加修改,让它自己产生乘数与被乘 数,然后计算随机的 7200位整数互乘,在Cyrix 6x86 pr166机器的纯DOS方式下耗时 7秒(用Borland C 3.1编译)。 经过改进,此算法效率可以提高约9 倍。 注意到以下事实:8216547 x 96785 将两数从个位起,每 3位分为节,列出乘法表,将斜线间的 数字相加; 8 216 547 96 785 768 20736 52512 6280 169560 429395 768 27016 222072 429395 将表中最后一行进行如下处理:从个位数开始,每一个方格里只保留三位数字,超出 1000 的部 分进位到前一个方格里; 768 27016 222072 429395 768+27 27016+222 222072+429 =795 =27238 =222501 395 795 238 501 395 所以8216547 x 96785 = 795238501395 也就是说我们在计算生成这个二维表时,不必一位一位地乘,而可以三位三位地乘;在累加时也是满1000进位。这样,我们在计算 m位整数乘以 n位整数,只需要进行 m x n / 9次乘法运算,再进行约(m + n) / 3次加法运算和(m + n) /3 次取模运算。总体看来,效率约是前一种算法的 9倍。 有人可能会想:既然能够三位三位地乘,为什么不4位 4位甚至5位5位地乘呢?那不是可以提高 16 乃至 25 倍效率吗?听我解来:本算法在累加表中斜线间的数字时,如果用无符号长整数(范围 0至~4294967295)作为累加变量,在最不利的情况下(两个乘数的所有数字均是 9),能够累加约4294967295/(999*999)=4300 次,也就是能够准确计算任意两个均不超过 12900(每次累加的结果"值"三位,故 4300*3=12900)位的整数相乘。如果 4 位 4 位地乘,在最不利的情况下,能够累加约4294967295/(9999*9999)=43 次,仅能够确保任意两个不超过 172 位的整数相乘,没有什么实用价值,更不要说5位了。 请看改进后的算法的实例程序: 该程序随机产生两个72xx位的整数,把乘数与积保存在 result.txt中。 在Borland C++ 3.1 中用 BCC -3 -O2 -G -mh -Z -f287 -pr -T- dashu.cpp 编译生成的exe文件在Cyrix 6x86 pr166的机器上运行耗时0.82 秒。 程序 2 清单: #include #include #include #include #include #define N 7200 //作 72xx 位的整数乘法 int max(int,int,int); int initarray(int a[]); void write(int a[],int l); FILE *fp; void main() { int a[5000]={0},b[5000]={0},k[10001]={0}; //声明存放乘数、被乘数与积的数组 clock_t start, end; //声明用于计时的变量 unsigned long c,d,e; //声明作累加用的无符号长整数变量 int i,j,la,lb,ma,mi,p,q,t; //声明其它变量 randomize(); //初始化随机数 la=initarray(a); //产生被乘数,并返回其长度 lb=initarray(b); //产生乘数,并返回其长度 if(lala)?lb:la; for (q=0;q=0;i--) //累加斜线间的数,i 为横纵坐标之和 { c=d; //将前一位的进位标志存入累加变量 c ma=max(0,i-la+1,i-lb+1); //求累加的下限 mi=(i>la-1)?(la-1):i; //求累加的上限 for(j=ma;j<=mi;j++) //计算出横纵坐标之和为 i 的单元内的数,并累加到 C 中 c+=(long)a[j]*b[i-j]; d=c/1000; //求进位标志 if(c>999) c%=1000; //取 c 的末三位 k[i]=c; //保存至表示乘积的数组 k[] } e=k[0]+1000*d; //求出乘积的最高位 end = clock();//停止计时 fp = fopen("result.txt", "w+"); //保存结果到 result.txt printf("\nThe elapsed time was: %3.4f\n", (end - start) / CLK_TCK); //打印消耗的时间 fprintf(fp,"%d",a[0]); //打印被乘数最高位 write(a,la); //打印被乘数其他位 fprintf(fp,"%d",b[0]); //打印乘数最高位 write(b,lb); //打印乘数其他位 fprintf(fp,"%ld",e); //打印乘积最高位 write(k,la+lb-1); //打印乘积其他位 fclose(fp); } max(int a,int b,int c) { int d; d=(a>b)?a:b; return (d>c)?d:c; } int initarray(int a[]) { int q,p,i; q=N+random(100); if(q%3==0) p=q/3; else p=q/3+1; for(i=0;iposted on 2009-12-06 23:26 chatler 阅读(1121) 评论(0)  编辑 收藏 引用 所属分类: Algorithm

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


<2009年4月>
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

常用链接

留言簿(10)

随笔分类(307)

随笔档案(297)

algorithm

Books_Free_Online

C++

database

Linux

Linux shell

linux socket

misce

  • cloudward
  • 感觉这个博客还是不错,虽然做的东西和我不大相关,觉得看看还是有好处的

network

OSS

  • Google Android
  • Android is a software stack for mobile devices that includes an operating system, middleware and key applications. This early look at the Android SDK provides the tools and APIs necessary to begin developing applications on the Android platform using the Java programming language.
  • os161 file list

overall

搜索

  •  

最新评论

阅读排行榜

评论排行榜