当前位置:文档之家› 高精度运算(C++)

高精度运算(C++)

万进制高精度运算(C++语言)目前在青少年信息学奥林匹克竞赛中所涉及到的高精度计算包括加(addition)、减(subtract)、乘(multiply)、除(divide)四种基本运算。

其中乘法分高精度数乘高精度数和单精度数乘高精度数两种,除法一般指两个单精度数相除,求解最终指定精度的解,找出循环节或输出指定精度位数的小数。

(注:高精度数与单精度数均指整数)主要的解题思想是利用在小学就曾学习过的竖式加减乘除法则,用程序语言实现存在的问题主要有如何存储高精度数的值,如何实现计算等问题。

一. 高精度数字的存储我们日常书写一个高精度数字,左侧为其高位,右侧为其低位,在计算中往往会因进位(carry )或借位(borrow )导致高位增长或减少,因此我们定义一个整型数组(int bignum[maxlen])从低位向高位实现高精度整数的存储,数组的每个元素存储高精度数中的一位。

(如下表所示)高精度数 3(高位)…… 7 9 4(低位)int bignum[i]n……21显然,在C++语言中,int 类型(4个字节/32位计算机)元素存储十进制的一位数字非常浪费空间,并且运算量也非常大,因此常将程序代码优化为万进制,即数组的每个元素存储高精数字的四位。

在后面的叙述过程中均以万进制为例介绍。

(为什么选择万进制,而不选择更大的进制呢?十万进制中的最大值99999相乘时得到的值是9999800001超过4个字节的存储范围而溢出,从而导致程序计算错误。

)在实际编写程序代码过程中常作如下定义: const int base=10000; const int maxlen=1000+1; int bignum[maxlen];说明:base 表示进制为万进制,maxlen 表示高精度数的长度,1个元素能存储4个十进制位,1000个元素就存储4000个十进制位,而加1表示下标为0的元素另有它用,常用作存储当前高精度数字的位数。

二. 各种运算的程序实现 (一)加法:首先回顾一下小学中曾学习的竖式加法,见图一:bignum1[] 9475 46 1243 bignum2[]918 1324 341 carry1 0 0 0 bignum_ans[]139313701584图一 加法的计算过程从上面的图中我们可以得知,做加法运算是从低位向高位进行,如果有进位,下一位进行相加时要加上进位,如果最高位已计算完还有进位,就要增加存储结果的位数,保存起进位来。

关于进位的处理,往往定义单独变量carry 进行存储,程序实现的过程如图二所示:初始化进位carry 赋初始值0,结果的位数为两个加数的最大位数。

当前位超过最高位了?处理当前位和进位NY还有进位么?N处理进位Y图二 加法的实现过程高精度加法程序代码(bignum1+bignum2→bignum_ans ):void addition(int *bignum1, int *bignum2, int *bignum_ans){ int carry=0; memset( bignum_ans, 0, sizeof(bignum_ans) );// memset 作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法。

*bignum_ans=*bignum1>*bignu2?*bignum1:*bignum2; for(int pos=1; pos<=*bignum_ans; pos++){ carry+=bignum1[pos]+bignum2[pos]; bignum_ans[pos]=carry%base; carry/=base; } while(carry){ bignum_ans[++*bignum_ans]=carry%base; carry/=base; } }说明:函数中的数组是引用传递,传递的是数组的首元素的地址,可用指针来代替,当前指针所指向的为0元素,后面的代码相同。

有的同学可能提出,在加法运算中的进位不管进制是多少,进位只可能出现1,用while 循环没有意义,在这里说明一下,为了跟后面乘法中出现的代码相匹配才采用这种处理方法,实现代码的统一性。

(二)减法:bignum1[] 132 9475 46 1243 bignum2[] 132 918 1324 341 borrow 0 1 0 0 bignum_ans[]85568722902图三 减法的计算过程图三表示出了减法的计算过程,与加法不同的地方是进位变成了借位,另外就是计算结果的位数可能会比被减数的位数少,因此在处理过程中要更要注意结果到底是多少位的。

其次,日常我们做减法时,如果被减数小于减数,我们是把两数反过来进行相减,在前面添加负号标识。

因此,我们在编写减法子函数时是约定bignum1大于bignum2的,调用时首先判断两个高精度数的大小,然后根据两数的大小决定如何调用。

减法的实现过程如图四所示:图四 减法的实现过程高精度数比较程序代码:int bignumcmp( int *bignum1, int *bignum2 ){ if (*bignum1-*bignum2) return *bignum1-*bignum2; for (int pos=*bignum1; pos>0; pos--)初始化借位borrow 赋初始值0,结果的位数为被减数的位数。

当前位超过最高位了?处理当前位和借位NY结果高位为0?N结束结果位数减1Yif ( bignum1[pos]-bignum2[pos] ) return bignum1[pos]-bignum2[pos]; return 0;}说明:bignum1>bignum2返回正整数,bignum1==bignum2返回0,bignum1<bignum2返回负整数。

解释:首先进行两数的位数的比较,如果位数相同再从高位向低位比较。

高精度减法程序代码(bignum1-bignum2→bignum_ans ):void subtract( int *bignum1, int *bignum2, int *bignum_ans ){ int borrow=0; memset( bignum_ans, 0, sizeof(bignum_ans) ); *bignum_ans=*bignum1; for(int pos=1; pos<=*bignum_ans; pos++){ bignum_ans[pos]=bignum1[pos]-borrow-bignum2[pos]; if(bignum_ans[pos]<0){ bignum_ans[pos]+=base; borrow=1; }else{borrow=0; } } while( !bignum_ans[*bignum_ans] ) --*bignum_ans; }(三)乘法:乘法的计算过程正如图五所示的从乘数的最低位起枚举每一位与被乘数相乘,累加到结果当中。

高精度乘高精度实际是多次调用单精度数乘高精高数运算。

1 2 3 4 X 4 3 2 1 (1) 1 2 3 4 (2) 2 4 6 8 (3) 3 7 0 2 (4) 4 9 3 65332114图五 乘法的计算过程首先看一下单精度数乘高精度数的实现过程,如图六所示:初始化进位carry 赋初始值0,结果的位数被乘数的位数。

当前位超过最高位了?处理当前位和进位NY还有进位么?N结束处理进位Y图六单精度乘高精度实现过程单精度乘高精度程序代码(n*bignum→bignum_ans):void SingleMultiply(int n, int *bignum, int *bignum_ans){int carry=0;memset(bignum_ans, 0, sizeof(bignum_ans);*bignum_ans=*bignum;for(int pos=1; pos<=*bignum_ans; pos++){carry+=n*bignum[pos];bignum_ans[pos]=carry%base;carry/=base;}while(carry){bignum_ans[++*bignum_ans]=carry%base;carry/=base;}}高精度数乘高精度数,实质就是在单精度数乘高精度数的基础上枚举各个乘数位与被乘数相乘,累计到结果当中。

其中乘数中的第J位与被乘数中的第I位相乘时,结果应该保存到结果的第I+J-1位中,因为如果存在进位的问题结果的位数可能为乘数与被乘数的位数和,也可能为两者位数和减一,这一点也应该单独处理。

过程就不再展示了,具体的请阅读下面的程序代码:高精度乘高精度程序代码(bignum1*bignum2→bignum_ans):void BignumMultiply( int *bignum1, int *bignum2, int *bignum_ans){int carry=0, i, j;memset(bignum_ans, 0, sizeof(bignum_ans) );for (j=1; j<=*bignum2; j++){for(i=1; i<=*bignum1; i++){bignum_ans[i+j-1]+=carry+bignum1[i]*bignum2[j];carry=bignum_ans[i+j-1]/base;bignum_ans[i+j-1]%=base;}i=j+*bignum1;while(carry){bignum_ans[i++]=carry%base;carry/=base;}}*bignum_ans=*bignum1+*bignum2;while( !bignum_ans[*bignum_ans] ) --*bignum_ans;}(四)除法:除法在高精度计算中是最为特殊的,在近几年联赛中并没有出现类似的题目,除法类的高精度题目会涉及到精度和循环节问题,在这里首先用表格分析两个例子:例一:3除以8,结果为0.375被除数 3 30 60 40商0 . 3 7 5余数 3 6 4 0例二:45除以56,结果为0.803(571428)被除数 45 450 20 200 320 400 80 240 160 480 商 0 . 8 0357 1428余数45220 32 40 824 16 48 32在例一中展示的为能被除尽的情形,能被除尽的条件是余数为0,而在例二中56并不能除尽45,出现571428这个循环节,出现循环节的条件是当前的余数曾经出现在前面求得的余数序列中。

相关主题