大数四则运算的实现

【摘要】大数运算不仅仅运用在密码学中,还运用在物理学、生物学、化学等一些科目的研究中。大数运算,意味着参加的值和计算结果通常是上百位数、上千位数甚至更大长度的整数。例如大家所熟知的圆周率π的值:在一般的数值计算中用到的圆周率不须要多大的精度,但在计算星球或星系上的体积面积时就要求π值计算的精度达到几百万位甚至更高,这样才能缩小误差。人工计算是远远不行了,而且本身误差也无法估计。只有在计算机中用大数运算求π值了。又如,考古学家计算石头内的碳元素衰变来考证地球形成的时间,更是将计算的结果精确到了百年以内。所以说大数的运算是涉及领域多,应用范广,与我们的生活息息相关。在此,我采用一个在java语言下实现计算大数运算的一个程序(http://student.csdn.net/space.php?uid=622078&do=blog&id=51862)为例,探讨一下大数的四则运算(加法、减法、乘法和除法)算法及代码。

 

一、大数存储的实现

大数的精度一般是少则几位,多则几万位。在java语言中定义的基本数据类型最多也就是long类型的19位(922,3372,0368,5477,5807),所以用它来存储大数肯定是不行的。但字符串类型的长度可以有很大,我们可以用它来存储。类结构如下:

package org.hbin.test;

/**
 * 超大整数
 * 
 * @file BigNumber.java
 * @log Revision 1.0
 * @author hbin
 * @date 2014-2-13
 *
 */
public class BigNumber
{
	/**
	 * 使用字符串来实现存储该超大整数
	 */
	private String data;

	public BigNumber(String data)
	{
		this.data = data;
	}

	public String getData()
	{
		return data;
	}

	public void setData(String data)
	{
		this.data = data;
	}
}

为了更加方便地构造一个超大整数,我们可以添加另外两个构造方法:
	public BigNumber(int n)
	{
		this.data = Integer.toString(n);
	}
	
	public BigNumber(long l)
	{
		this.data = Long.toString(l);
	}

为了更加方便地输出显示该超大整数,我们 重写 (Overriding,也叫覆盖) toString()方法:
	public String toString()
	{
		return data;
	}

二、各种算法

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

         注意:1a>b>0时直接运算,其他情况作相应转换后再运算(如b>a>0时等价于-(a-b)a>0>b时转换为a+(-b)加法运算等)。

         2、结果可能会出现前面有一些0的情况,可以在创建大数对象时进行处理。

3:乘法运算的实现

乘法运算可以看作是加法运算。如:a*b=3*4可以看作((3+3)+3)+3,43的和。具体做法是:定义一个循环,从1b,每次都做一次加法运算,最终计算出结果。

弊端:若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,否则跳出当前循环。当循环结束时,记录的值就是要求得的商。

以上三种思路,思路一和思路二十分类似,我先想了思路一的程序实现,但感觉有点儿复杂,就放弃了。重点考虑了思路三的具体实现,可是测试的时候才发现一个问题,那就是它的效率太低了,尤其是当一个很大的数除以一个很小的数时。

 

以上是我在做大数类实现时的查找的资料或者自己的一些想法、笔记,写下来以备忘!好记性不如烂笔头嘛!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值