【摘要】大数运算不仅仅运用在密码学中,还运用在物理学、生物学、化学等一些科目的研究中。大数运算,意味着参加的值和计算结果通常是上百位数、上千位数甚至更大长度的整数。例如大家所熟知的圆周率π的值:在一般的数值计算中用到的圆周率不须要多大的精度,但在计算星球或星系上的体积面积时就要求π值计算的精度达到几百万位甚至更高,这样才能缩小误差。人工计算是远远不行了,而且本身误差也无法估计。只有在计算机中用大数运算求π值了。又如,考古学家计算石头内的碳元素衰变来考证地球形成的时间,更是将计算的结果精确到了百年以内。所以说大数的运算是涉及领域多,应用范广,与我们的生活息息相关。在此,我采用一个在java语言下实现计算大数运算的一个程序(http://student.csdn.net/space.php?uid=622078&do=blog&id=51862)为例,探讨一下大数的四则运算(加法、减法、乘法和除法)算法及代码。
一、大数存储的实现
大数的精度一般是少则几位,多则几万位。在java语言中定义的基本数据类型最多也就是long类型的19位(922,3372,0368,5477,5807),所以用它来存储大数肯定是不行的。但字符串类型的长度可以有很大,我们可以用它来存储。类结构如下:
二、各种算法
1:加法运算的实现
加法运算还是比较容易的。可以想想我们刚学习加法运算的时候:先从低位算起,对相应的位相加运算,再加上前一位的进位(如果存在),再判断本位是否有进位,如果有则把本位数字减去权(这里是10),再置进位为1。然后计算下一位(循环)。
注意:
1、循环结束的时候,如果有进位,应该在当前结果前加上一个1。
2、负数暂不考虑(两个负数的和等于它们绝对值的和的相反数,正数和负数的和的运算可以看成减法运算)。
附代码:
/**
* 加上超大整数n<br/>
* 用当前数作为被加数,参数n作为加数,求和。结果重新赋值给当前数
*
* @param n
* 加数
*/
public void add(BigNumber n)
{
data = sum(this, n).data;
}
/**
* 计算两个超大整数的和
*
* @param a
* 被加数
* @param b
* 加数
* @return 两个数的和
*/
public static BigNumber sum(BigNumber a, BigNumber b)
{
String str = ""; // 储存结果
int m = a.data.length();
int n = b.data.length();
int temp = 0; // 当前相加的和
int oldTemp = 0; // 上一次相加的和
// 为便于循环,要求a的位数必须不小于b的位数,否则求b+a的结果
if (m >= n)
{
for (int i = 0; i < m; i++)
{
int j = 0;
if (i < n)
{
j = b.data.charAt(n - i - 1) - '0';
}
temp = a.data.charAt(m - i - 1) - '0' + j + oldTemp / 10;
oldTemp = temp;
str = temp % 10 + str;
}
// 循环结束时如果有进位,则首位加1
if (oldTemp >= 10)
{
str = 1 + str;
}
return new BigNumber(str);
} else
{
return BigNumber.sum(b, a);
}
}
2:减法运算的实现
减法稍微复杂一点儿,但也不用担心。算法也是从低位开始,对应位相减再减去上一位的借位(如果有)的值如果小于0则加上权(这里是10),并置借位为1。
注意:1、a>b>0时直接运算,其他情况作相应转换后再运算(如b>a>0时等价于-(a-b),a>0>b时转换为a+(-b)加法运算等)。
2、结果可能会出现前面有一些0的情况,可以在创建大数对象时进行处理。
3:乘法运算的实现
乘法运算可以看作是加法运算。如:a*b=3*4可以看作((3+3)+3)+3,即4个3的和。具体做法是:定义一个循环,从1到b,每次都做一次加法运算,最终计算出结果。
弊端:若b非常大,那么该算法效率极低。
【改进方法一】运算前先判断两个参数的大小,使用较小的数作为循环次数。
【改进方法二】依据竖式计算的原理
将乘数的最后一位与被乘数的每一位相乘,记录结果,然后用前一位与被乘数的每一位相乘,记录结果并左移一位,以此类推,直到计算完第一位。再将各项结果相加,得出最后结果。
4:除法运算的实现(Am/Bn)
最初是想着根据除法竖式来实现:从被除数A高位取出大于除数B的值(n位或n+1位)作除,记录当前的商和余数,取被除数的下一位与当前余数连接然后再除以除数B,记录当前的商和余数,以此类推,直到计算完最后一位。将各项商移位相加即得结果。
和同学讨论时又获得一种思路:从被除数A高位取出大于除数B的值(n位或n+1位)作除,在得到的商后面补零(零的个数依次从A高位取值后剩余的位数来决定)并记录,然后用被除数A减去除数与记录值的乘积,将得到的差作为新的被除数。以此类推,直到计算完最后一位。将各项结果相加,得出最后结果。
经过一番思考,又想到一种方法:从被除数A高位取出n位并判断其是否大于除数B的值,如果是则在记录的1后面补m-n个零,否则在记录的1后面补m-n-1个零。然后有一个双层循环,外层根据该记录的长度来循环,内层从0-9循环,在循环体中,如果被除数A减去除数B与该记录的积大于除数B与位权值(1后面有n个零,n由当前循环位置来确定),则记录的当前位增加1,否则跳出当前循环。当循环结束时,记录的值就是要求得的商。
以上三种思路,思路一和思路二十分类似,我先想了思路一的程序实现,但感觉有点儿复杂,就放弃了。重点考虑了思路三的具体实现,可是测试的时候才发现一个问题,那就是它的效率太低了,尤其是当一个很大的数除以一个很小的数时。
以上是我在做大数类实现时的查找的资料或者自己的一些想法、笔记,写下来以备忘!好记性不如烂笔头嘛!