`
frank-liu
  • 浏览: 1686315 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

大整数的求和和乘积

 
阅读更多

简介:

    对于一些大的整数,由于计算机本身能够显示的内置数据类型精度有限,在处理一些比较大的整数运算时就不能适用。因此需要考虑用一些结构来辅助运算。一种典型的方式就是通过数组的方式来保存这些大整数。然后通过模拟手工运算的逐位运算。下面对整数的加法和乘法做一个总结。

加法:

位相加的关系:

    笼统的来说,加法中数组的每一个元素都对应着整数的每一位。当两个数相加的时候,要把前面的进位和当前的和相加,然后根据结果来进行进位。如果仔细考虑的话,会发现有如下的情况存在:

假设有两个数组a[], b[], 和进位标志carryBit.

如果a[i] + b[i] + carryBit > 当前的数制,则carryBit要置位,相加的结果要减去当前的数制。

比如a[i] = 5, b[i] = 7, carryBit = 1。考虑10进制的情况。则相加的结果为13. 那么根据讨论,后面一位的进位标志为1, 当前的结果为13 - 10 = 3.

    如果对上一步的讨论做进一步的提炼,我们会发现他们的运算实际上满足这么一个关系:

当前位的求和结果 = (a[i] + b[i] + carryBit) % 10,

下一位的进位标志(carryBit) = (a[i] + b[i] + carryBit) / 10

    对于更加通用的数制运算,我们可以发现有同样对应的关系:

 当前位的求和结果 = (a[i] + b[i] + carryBit) % 数制,

下一位的进位标志(carryBit) = (a[i] + b[i] + carryBit) / 数制。

数据的表示:

        在前面的逻辑关系理请之后,剩下的就是如何保存对应的数据了。一般我们展示的数字比如:12345,一般都是高位在前面,低位在后面。而在进行数组的加法运算的时候,需要从低位到高位,那么在数组中的保存方式比较合理的方式应该为int[] a = {5, 4, 3, 2, 1}.这样,当两个数组相加的时候,我们只要从数组的开头遍历就可以了。同时,因为我们保存数据的顺序和显示数据的顺序是相反的,如果后续需要显示对应的数字的话,则需要倒序输出。

        另外,两个数字相加之后,保存相加结果的数组长度必须要加1,这样才能保证有足够的空间将结果保存下来。

实现:

        考虑一种最简单的情况,假设两个数组的长度相同。有了前面的讨论,我们就可以很容易实现如下的方法:

public static int[] genericPlus(int[] a, int[] b, int numberSystem)
{
	int[] c = new int[a.length + 1];
	int carryBit = 0;
	for(int i = 0; i < a.length; i++)
	{
		c[i] = (a[i] + b[i] + carryBit) % numberSystem;
		carryBit = (a[i] + b[i] + carryBit) / numberSystem;
	}
	c[a.length] = carryBit;
	return c;
}

    当然,这是一种比较理想的情况。经过前面一些网友的指正,更通用的情况下还需要考虑两个数组长度不相同的情况。那么,一个详细的实现方法如下:

public static int[] comparePlus(int[] a, int[] b, int numberSystem)
{
	if(a.length == b.length)
	{
		// Common routine
		int[] c = new int[a.length + 1];
		int carryBit = 0;
		for(int i = 0; i < a.length; i++)
		{
			c[i] = (a[i] + b[i] + carryBit) % numberSystem;
			carryBit = (a[i] + b[i] + carryBit) / numberSystem;
		}

		c[a.length] = carryBit;

		return c;
	}
	else if(a.length > b.length)
	{
		// Routine 1
		int[] c = new int[a.length + 1];
		int carryBit = 0;
		int i;
		for(i = 0; i < b.length; i++)
		{
			c[i] = (a[i] + b[i] + carryBit) % numberSystem;
			carryBit = (a[i] + b[i] + carryBit) / numberSystem;
		}

		while(i < a.length)
		{
			c[i] = (a[i] + carryBit) % numberSystem;
			carryBit = (a[i] + carryBit) / numberSystem;
		}

		c[a.length] = carryBit;

		return c;
	}
	else
	{
		// Routine 2
	}
}

 这个部分的代码总的思路就是先判断两个数组的长度是否相同,如果相同则分配一个长度+1的数组,然后遍历数组,并将结果返回。在一个数组长度比另外一个大的情况下,则需要先遍历完短数组的那一段。剩下的部分遍历时则运算的结果变为:c[i] = (a[i] + carryBit) % numberSystem; carryBit = (a[i] + carryBit) / numberSystem;(这里假设a是长的那个)。剩下的那部分也可以照此类推。这里将三个条件的处理都放在一个方法里面了。实际代码里可以分别定义不同的方法来实现。

乘法:

        有了前面加法讨论的基础,再考虑乘法则相对方便一些了。我们在考虑一下原来乘法的思路。对于两个数组a[], b[],假定结果数组为c[]。我们对数组相乘的步骤一般如下:

1. 取b[]中的一个元素,和a[]相乘,得到一个a.length + 1长度的中间数组。

经过遍历b[],我们就得到b.length长度这么多个中间数组。

2. 对于这些中间数组,如果对应的是b[i]乘积的结果,则后面求和的时候将这个数组左移i位,然后和总结果数组c[]相加。

这样得到的结果就是乘积。对于两个数组相乘,其结果的长度为两个数组长度之和。

实现:

        经过前面的分析,我们可以得到如下的代码:

 

public static int[] arrayMultiply(int[] a, int[] b)
{
	int[] c = new int[a.length + b.length];

	for(int i = 0; i < b.length; i++)
	{
		// Generate a multiply result from b[i] * a
		int[] middleResult = new int[a.length + 1];
		int carryBit = 0;
		for(int j = 0; j < a.length; j++)
		{
			middleResult[j] = (b[i] * a[j] + carryBit) % 10;
			carryBit = (b[i] * a[j] + carryBit) / 10;
		}
		middleResult[a.length] = carryBit;
			
		// Plus middle result to c
		carryBit = 0;
		for(int k = 0; k < middleResult.length; k++)
		{
			int sum  = middleResult[k] + c[i + k] + carryBit;
			c[i + k] = sum  % 10;
			carryBit = sum / 10;
		}
	}
	return c;
}

注:前面这种实现只是针对10进制的方式。如果需要换成更加通用的方式,可以将10换成传进来的进制参数。当然函数签名也要做相应的修改。

另外,这种写法只是一个比较粗略的写法,从可读性的角度来说完全可以将生成中间结果数组和将中间结果数组加到最终结果数组的两部分分成两个方法。

 数据展示的问题:

        前面保存数据的时候是采用数组的方式,而且存储的方式和我们显示的方向是相反的。经过前面网友的提醒,在展示数据的时候,可能会存在一个问题。就是如果两个数相加的时候,并不一定会进位。前面的运算结果是直接分配了增加一个长度位的数组。当没有进位的时候,保存在最高位的数字是0. 因此,一种办法是在展示数字的时候,判断一下最高位来跳过这个0.

比如我们一个输出数组元素的方法,其实现则应该如下:

public static void printBinaryArrays(int[] array)
{
    if(array[array.length - 1] == 0)
    {
	for(int i = array.length - 2; i >= 0; i--)
		System.out.print(array[i]);
    }
    else
    {
        for(int i = array.length -1; i >= 0; i--)
            System.out.print(array[i]);
    }
    System.out.println();
}

 

总结:

        这里主要讨论了大整数的加法和乘法,对于减法来说,思路也很近似,相信看过这个之后大家也该知道怎么做了。当然,这种大整数的运算在本文里的实现并不是可以无穷大。目前的一个实现是长度最多到Integer.MAX_VALUE,也就是2^31 -1这么多位的数字。如果需要更多位数的话,可能就需要Long数据类型甚至一些自定义的类型了。这种问题本身不是很难,只是如果要在很短的时间内理清思路并不出问题,确实还是有点挑战性。前面kissinger同学曾经在这个问题上栽了,在此聊表慰问一下吧:)。

补充:

    一些相加和相乘的过程中,可能会出现的各种细节问题,比如长度限制,不同长度数据的运算等等。感谢前面一些网友的指正。

0
2
分享到:
评论
10 楼 shihengli2010 2017-07-06  
while(i < a.length)
{
c[i] = (a[i] + carryBit) % numberSystem;
carryBit = (a[i] + carryBit) / numberSystem;
}
少了一个i++
9 楼 frank-liu 2013-01-15  
inta 写道
1、两个数据长度不一样未考虑
2、在数据长度相同的情况下你都是直接将新数组长度加1, 未做9+1这种临界情况的判断,产生的结果中的0 如何能知道是有用的数 还是没用的

非常感谢,已经在文中作了对应的修改。
8 楼 lv12312 2013-01-14  
inta 写道
1、两个数据长度不一样未考虑
2、在数据长度相同的情况下你都是直接将新数组长度加1, 未做9+1这种临界情况的判断,产生的结果中的0 如何能知道是有用的数 还是没用的
+1,漏洞比较多
7 楼 inta 2013-01-14  
1、两个数据长度不一样未考虑
2、在数据长度相同的情况下你都是直接将新数组长度加1, 未做9+1这种临界情况的判断,产生的结果中的0 如何能知道是有用的数 还是没用的
6 楼 frank-liu 2013-01-14  
zhukewen_java 写道
frank-liu 写道
zhukewen_java 写道
zhukewen_java 写道
虽然没看完,但楼主的代码是有bug的。有一处是在for循环那里

因为你标题敢写是大整数,所以我才敢指出这有bug。你的代码通不过边界测试。

for循环里面,能具体指明是哪个问题吗?前面的代码是比较笼统的写法,没有检查传入的数组是空或者长度为0的情况。

当大整数的很大时(比如位数达到了Integer.maxvalue),会发生整数越界的情况。

是的, 如果位数达到2^32及以上,理论上来说,光要用一个内置数据类型来表示位的长度都可能保存不下的。
5 楼 zhukewen_java 2013-01-14  
frank-liu 写道
zhukewen_java 写道
zhukewen_java 写道
虽然没看完,但楼主的代码是有bug的。有一处是在for循环那里

因为你标题敢写是大整数,所以我才敢指出这有bug。你的代码通不过边界测试。

for循环里面,能具体指明是哪个问题吗?前面的代码是比较笼统的写法,没有检查传入的数组是空或者长度为0的情况。

当大整数的很大时(比如位数达到了Integer.maxvalue),会发生整数越界的情况。
4 楼 frank-liu 2013-01-14  
zhukewen_java 写道
zhukewen_java 写道
虽然没看完,但楼主的代码是有bug的。有一处是在for循环那里

因为你标题敢写是大整数,所以我才敢指出这有bug。你的代码通不过边界测试。

for循环里面,能具体指明是哪个问题吗?前面的代码是比较笼统的写法,没有检查传入的数组是空或者长度为0的情况。
3 楼 zhukewen_java 2013-01-14  
zhukewen_java 写道
虽然没看完,但楼主的代码是有bug的。有一处是在for循环那里

因为你标题敢写是大整数,所以我才敢指出这有bug。你的代码通不过边界测试。
2 楼 frank-liu 2013-01-14  
zhukewen_java 写道
虽然没看完,但楼主的代码是有bug的。有一处是在for循环那里

你是指的乘法那一部分吗?愿闻其详。
1 楼 zhukewen_java 2013-01-14  
虽然没看完,但楼主的代码是有bug的。有一处是在for循环那里

相关推荐

    运算符重载(c++ 最会的重载100).rar

    在实际应用中,运算符重载可以极大地提高代码的易读性。例如,如果你定义了一个表示复数的类,可以重载`+`和`*`运算符,使得复数的加法和乘法像数学公式一样直观: ```cpp ComplexNumber operator+(const Complex...

    大整数运算

    通过上述分析,我们可以理解到大整数加法、减法和乘法的基本实现思路和关键步骤,这对于深入学习高级算法和数据结构至关重要。在实际应用中,掌握这些原理不仅能帮助我们更好地设计和优化算法,还能提升解决实际问题...

    1到n的阶乘求和

    阶乘求和是一个在计算机科学和数学中常见的问题,它涉及到序列的计算和求和。在本问题中,我们关注的是从1到n的所有整数的阶乘的总和。阶乘是一个数与小于等于它的所有正整数的乘积,表示为n!。例如,5! = 5 × 4 ×...

    关于整数的整数因子和问题的若干研究

    整数因子和问题是一个在数论领域中常见的问题,特别是在解决算法竞赛如ACM中的数论题目时常常会遇到。该问题通常要求计算一个...通过深入学习和实践,我们可以更有效地处理各种整数因子和问题,无论它们的规模多大。

    相乘求和的计算器

    这个计算器的独特之处在于它不仅支持基本的加法和乘法,而且特别设计了一个功能,允许用户输入一系列的整数或浮点数,然后进行逐对相乘,最后将所有乘积相加。这在处理涉及多个数值乘积累加的计算场景时非常实用,...

    长整数乘法

    3. 两个长整数的乘积的函数调用了部分求和的函数和从表头得到表尾的函数,以及将一个长整数前后数值交换的函数以及显示一个长整数的函数。 知识点4:详细设计 a. 两个长整数的输入 使用头插法的方式,当输入一个4...

    用 VB 做的一个 《阶乘求和》

    用户可以通过运行这个程序,输入不同的整数n,观察阶乘求和的结果,从而学习和理解阶乘和求和的概念。 总结一下,本项目涉及了VB编程中的基本功能,如函数定义、递归调用、循环控制以及消息提示。同时,它也涉及到...

    求阶乘和_阶乘_求和_vb6_

    总结一下,这个VB6程序的核心是利用阶乘的定义(即连续整数的乘积)和累加求和的概念,通过循环结构计算阶乘之和。对于初学者,这样的程序有助于巩固对基础编程概念的理解,并逐步提升编程技能。

    阶乘求和源代码

    阶乘是数学中的一个运算,表示一个正整数n与小于等于n的所有正整数的乘积,通常用"!"来表示。例如,5! = 5 × 4 × 3 × 2 × 1 = 120。 在C++编程语言中,我们可以编写一个程序来解决这个问题。以下是一个简单的...

    Java阶乘求和计算范例.rar

    阶乘是一个数学概念,表示一个正整数n的所有小于等于n的正整数的乘积,通常用n!表示。例如,5!(5的阶乘)等于5 * 4 * 3 * 2 * 1 = 120。这个任务要求我们计算从1到20所有整数的阶乘,并将这些阶乘的值相加。 首先...

    递归求和与计算阶乘1

    阶乘是一个数学概念,表示一个正整数`n`与小于`n`的所有正整数的乘积。例如,5的阶乘(5!)等于5 * 4 * 3 * 2 * 1。递归方法计算阶乘通过不断地调用自身来实现,每次调用将`n`减1,直到`n`等于0,这时返回1作为基础...

    范围求和 II1

    最后,返回 a 和 b 的乘积,即为矩阵中最大整数出现的次数。 这种解法的关键在于理解每个操作会累加矩阵中的元素,并且目标是找到所有操作共同影响的区域。由于每个操作都会增加指定区域内的所有元素,所以最大整数...

    级数求和方法大全

    2. **级数的收敛性**:如果存在一个实数 \(S\) 使得对任何正整数 \(N\),级数的部分和 \(S_N = \sum_{n=1}^{N} a_n\) 的极限 \(\lim_{N \to \infty} S_N = S\) 存在,则称该级数收敛于 \(S\)。 3. **级数的类型**:...

    n阶求和 (源码)

    阶乘是指所有小于及等于一个正整数的正整数的乘积,通常用符号“!”表示。例如,5的阶乘写作5! = 5 × 4 × 3 × 2 × 1 = 120。阶乘在概率论、组合数学以及许多其他数学领域都有广泛的应用。 ##### 2. 算法优化 ...

    C语言实现数据结构多项式求和问题

    例如,节点可能包含一个整数系数和一个整数指数,表示多项式的某一项 \(ax^n\)。 在C语言中实现多项式求和,我们需要创建两个链表来分别表示两个多项式,每个链表的节点包含系数和指数。然后,我们需要设计一个算法...

    输入两个正整数m和n求其最大公约数和最小公倍数.docx

    - 最小公倍数是能够同时整除两个或多个整数的最小正整数,可以通过两个数的乘积除以它们的最大公约数得到。 2. 字符统计: - 在编程中,可以遍历输入字符串,通过条件判断统计字母、空格、数字和其他字符的数量。...

    输入两个正整数m和n求其最大公约数和最小公倍数.pdf

    1. **最大公约数(GCD)和最小公倍数(LCM)**:使用欧几里得算法可以计算两个正整数的最大公约数,而最小公倍数可以通过两数乘积除以它们的最大公约数得到。 2. **字符计数**:在字符串处理中,统计字母、空格、...

    「Python循环结构」使用while循环实现求和和阶乘.docx

    阶乘(Factorial)是一个数的所有小于等于它的正整数的乘积。例如,5的阶乘(5!)就是1 * 2 * 3 * 4 * 5。我们可以使用while循环计算任意非负整数的阶乘: ```python number = 5 # 需要求阶乘的数 factorial = 1 # ...

    Java实现 LeetCode 598 范围求和 II(最小值相乘)

    操作用二维数组表示,其中的每个操作用一个含有两个正整数 a 和 b 的数组表示,含义是将所有符合 0 &lt;= i &lt; a 以及 0 &lt;= j &lt; b 的元素 M[i][j] 的值都增加 1。 在执行给定的一系列操作后,你需要返回矩阵...

    输入数阶乘求和

    (n的阶乘),即所有小于及等于n的正整数的乘积。例如,5! = 5 × 4 × 3 × 2 × 1 = 120。特别地,0! 和 1! 的值都定义为1。 ##### 2. 阶乘递归算法 程序中使用了递归方法来计算阶乘,这是一种常见的计算阶乘的...

Global site tag (gtag.js) - Google Analytics