两个大整数相乘
不用BigInteger和Long,实现大整数相乘。
看代码细节,数组越界啊、int溢出
思路:模拟手写乘法的思路,用数B的每一位与数A相乘,然后把中间结果加起来。
public class TestMult { public static void main(String[] args) { long timeStart = System.currentTimeMillis(); System.err.println("\nresult=" + getMult("99", "19")); System.err.println("\nresult=" + getMult("99", "99")); System.err.println("\nresult=" + getMult("123456789", "987654321")); System.err.println("\nresult=" + getMult("12345678987654321", "98765432123456789")); System.err.println("\ntake time: " + (System.currentTimeMillis() - timeStart)); } public static String getMult(int bigIntA, int bigIntB) { return getMult(String.valueOf(bigIntA), String.valueOf(bigIntB)); } public static String getMult(String bigIntA, String bigIntB) { int length = bigIntA.length() + bigIntB.length(); // if (length < 10) { // Integer.MAX_VALUE = 2147483647; // return String.valueOf(Integer.valueOf(bigIntA) * Integer.valueOf(bigIntB)); // } int[] result = new int[length]; // 保证长度足够容纳 int[] aInts = reverse(toIntArray(bigIntA)); // 将大数拆分为数组,并反转 让个位从0开始 int[] bInts = reverse(toIntArray(bigIntB)); // 也可以直接在转数组时顺便反转 int maxLength = 0; // 保存中间结果的最大长度,便于后面累加的数组 int[][] tempDatas = new int[bInts.length][]; // 储存中间已移位的结果,之后用于累加 int extra = 0; // 进位 for (int i = 0; i < bInts.length; i++) { int[] singleResult = new int[aInts.length]; // bigIntB中每一位与bigIntA的乘积 extra = 0; // 进位清零,否则会影响后面结果 for (int j = 0; j < aInts.length; j++) { int t = bInts[i] * aInts[j] + extra; singleResult[j] = t % 10; extra = t / 10; } if (extra > 0) { // 乘积最后进位,需要拓展一位存储extra int[] temp = new int[aInts.length + 1]; for (int m = 0; m < singleResult.length; m++) { temp[m] = singleResult[m]; } temp[temp.length-1] = extra; singleResult = temp; } singleResult = insertZero(singleResult, i, true); // 移位加0 print(singleResult); // 打印中间结果 tempDatas[i] = singleResult; // 保存中间结果 if (singleResult.length > maxLength) { // 获取位数最多的中间结果 maxLength = singleResult.length; } } // 将中间结果从个位开始累加 extra = 0; for (int k = 0; k < maxLength; k++) { int sum = extra; for (int[] datas : tempDatas) { if (k < datas.length) { sum += datas[k]; } } result[k] = sum % 10; extra = sum / 10; } if (extra > 0) { // 99*19 result[maxLength] = extra; // 在初始化result[]时,已确认不会越界 } // 将计算结果转为数字字符串 StringBuilder builder = null; for (int i = result.length - 1; i >= 0; i--) { if (result[i] != 0 && builder == null) { // 去除高位多余的0 builder = new StringBuilder(); } if(builder != null) { builder.append(result[i]); // 会将结果反转,之前为便于计算,使下标为0的为个位 } } return builder.toString(); } /** * 将数字字符串拆解为int数组 */ static int[] toIntArray(String bigNumberStr) { char[] cs = bigNumberStr.toCharArray(); int[] result = new int[cs.length]; for (int i = 0; i < cs.length; i++) { result[i] = cs[i] - '0'; } return result; } /** * 在数组插入0做偏移,例如与百位相乘时,结果需要加两个0 */ static int[] insertZero(int[] datas, int count, boolean hasReversed) { if (count <= 0) { return datas; } int[] result = new int[datas.length + count]; if (hasReversed) { for (int i = result.length - 1; i >= 0; i--) { int index = i - count; if (index >= 0) { result[i] = datas[index]; } else { result[i] = 0; } } } else { for (int i = 0; i < result.length - 1; i++) { if (i < datas.length) { result[i] = datas[i]; } else { result[i] = 0; } } } return result; } /** * 数组反转 */ static int[] reverse(int[] datas) { for (int i = 0; i < datas.length / 2; i++) { int temp = datas[i]; datas[i] = datas[datas.length - i - 1]; datas[datas.length - i -1] = temp; } return datas; } /** * 打印数组 */ static void print(int[] datas) { for (int i = 0; i < datas.length; i++) { System.out.print(datas[i] + ", "); } System.out.println(); } }
优化后
public class TestMulti { public static void main(String[] args) { long timeStart = System.currentTimeMillis(); System.err.println("\nresult=" + getMult("99", "19")); System.err.println("\nresult=" + getMult("99", "99")); System.err.println("\nresult=" + getMult("123456789", "987654321")); System.err.println("\nresult=" + getMult("12345678987654321", "98765432123456789")); System.err.println("\ntake time: " + (System.currentTimeMillis() - timeStart)); } public static String getMult(String bigIntA, String bigIntB) { int maxLength = bigIntA.length() + bigIntB.length(); // if (maxLength < 10) { // Integer.MAX_VALUE = 2147483647; // return String.valueOf(Integer.valueOf(bigIntA) * Integer.valueOf(bigIntB)); // } int[] result = new int[maxLength]; int[] aInts = new int[bigIntA.length()]; int[] bInts = new int[bigIntB.length()]; for (int i = 0; i < bigIntA.length(); i++) { aInts[i] = bigIntA.charAt(i) - '0';// bigIntA字符转化为数字存到数组 } for (int i = 0; i < bigIntB.length(); i++) { bInts[i] = bigIntB.charAt(i) - '0';// bigIntB字符转化为数字存到数组 } int curr; // 记录当前正在计算的位置,倒序 int x, y, z; // 记录个位十位 for (int i = bigIntB.length() - 1; i >= 0; i--) { curr = bigIntA.length() + i; // 实际为 maxLength - (bigIntB.length() - i) 简化得到 for (int j = bigIntA.length() - 1; j >= 0; j--) { z = bInts[i] * aInts[j] + result[curr]; // 乘积 并加上 上一次计算的进位 x = z % 10;// 个位 y = z / 10;// 十位 result[curr] = x; // 计算值存到数组c中 result[curr - 1] += y; // curr-1表示前一位,这里是进位的意思 curr--; } } int t = 0; for (; t < maxLength; t++) { if (result[t] != 0) { break; // 最前面的0都不输出 } } StringBuilder builder = new StringBuilder(); if (t == maxLength) { // 结果为0时 builder.append('0'); } else { // 结果不为0 for (int i = t; i < maxLength; i++) { builder.append(result[i]); } } return builder.toString(); } }
大整数乘法,就是乘法的两个乘数比较大,最后结果超过了整型甚至长整型的最大范围,此时如果需要得到精确结果,就不能常规的使用乘号直接计算了。没错,就需要采用分治的思想,将乘数“分割”,将大整数计算转换为小整数计算。
在这之前,让我们回忆一下小学学习乘法的场景吧。个位数乘法,是背诵乘法口诀表度过的,不提也罢;两位数乘法是怎么做的呢?现在就来一起回忆下12*34吧:
3 4 (是不是很多小朋友喜欢将大的整数作为被乘数的,呵呵)
*1 2
---------
6 8
3 4
---------
4 0 8
接下来就是找规律了。其实大家多做几个两位数的乘法,都会发现这个规律:AB * CD = AC (AD+BC) BD 。没错,这里如果BD相乘超过一位数,需要进位(这里二四得八,没有进位);(AD+BC+低位进位)如果超过一位数,需要进位(就像刚才的3*1,最后+1得4的操作)。
到这里可以看出,我们任意位数的整数相乘,最终都是可以转化为两位数相乘。当然,不同位的两位数乘的结果,最后应该如何拼接呢?这需要我们来找下更深层次的规律了。这下来个四位数的乘法,找找感觉:
1 2 3 4
* 5 6 7 8
------------------------
9 8 7 2
8 6 3 8
7 4 0 4
6 1 7 0
------------------------
7 0 0 6 6 5 2
这个结果看起来也没什么特别的,如果按照我们分治的思想,转换为两位数相乘,之间能否有些关系呢?
1234分为 12(高位)和34(低位);5678分为56(高位)和78(低位)
高位*高位结果:12*56=672
高位*低位+低位*高位:12*78+34*56=936+1094=2840
低位*低位结果:34*78=2652
这里要拼接了。需要说明的是,刚才我们提到两位数分解成一位数相乘的规则:超过一位数,需要进位。同理(这里就不证明了),两位数乘以两位数,结果超过两位数,也要进位。
从低位开始:低两位:2652,26进位,低位保留52;中间两位,2840+26(低位进位)=2866,28进位,中位保留66;高位,672+28(进位)=700,7进位,高位保留00。再往上就没有了,现在可以拼接起来:最高位进位7,高两位00,中位66,低位52,最后结果:7006652。
规律终于找到了!任意位数(例如N位整数相乘),都可以用这种思想实现:低位保留N位数字串,多余高位进位;高位要加上低位进位,如果超过N位,依然只保留N位,高位进位。(如果是M位整数乘以N位整数怎么办?高位补0,凑成一样位数的即可,不赘述。)
分治的规律找到了,接下来就是具体实现的思想了。
没啥新花样,依然是递归思想(这里为了简化,就不递归到两位数相乘了,4位数相乘,计算机还是能够得到精确值的):
1. 如果两个整数M和N的长度都小于等于4位数,则直接返回M*N的结果的字符串形式。
2. 如果如果M、N长度不一致,补齐M高位0(不妨设N>M),使都为N位整数。
3. N/2取整,得到整数的分割位数。将M、N拆分成m1、m2,n1,n2。
4. 将m1、n1;m2、n1;m1、n2;m2、n2递归调用第1步,分别得到结果AC(高位)、BC(中位)、AD(中位)、BD(低位)。
5. 判断BD位是否有进位bd,并截取bd得到保留位BD';判断BC+AD+bd是否有进位abcd,并截取进位得到保留位ABCD';判断AC+abcd是否有进位ac,并截取进位得到保留位AC'。
6. 返回最终大整数相乘的结果:ac AC' ABCD' BD'。
import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 大整数乘法 * @author Maxy * */ public class BigIntMultiply { //规模只要在这个范围内就可以直接计算了 private final static int SIZE = 4; // 此方法要保证入参len为X、Y的长度最大值 private static String bigIntMultiply(String X, String Y, int len) { // 最终返回结果 String str = ""; // 补齐X、Y,使之长度相同 X = formatNumber(X, len); Y = formatNumber(Y, len); // 少于4位数,可直接计算 if (len <= SIZE) { return "" + (Integer.parseInt(X) * Integer.parseInt(Y)); } // 将X、Y分别对半分成两部分 int len1 = len / 2; int len2 = len - len1; String A = X.substring(0, len1); String B = X.substring(len1); String C = Y.substring(0, len1); String D = Y.substring(len1); // 乘法法则,分块处理 int lenM = Math.max(len1, len2); String AC = bigIntMultiply(A, C, len1); String AD = bigIntMultiply(A, D, lenM); String BC = bigIntMultiply(B, C, lenM); String BD = bigIntMultiply(B, D, len2); // 处理BD,得到原位及进位 String[] sBD = dealString(BD, len2); // 处理AD+BC的和 String ADBC = addition(AD, BC); // 加上BD的进位 if (!"0".equals(sBD[1])) { ADBC = addition(ADBC, sBD[1]); } // 得到ADBC的进位 String[] sADBC = dealString(ADBC, lenM); // AC加上ADBC的进位 AC = addition(AC, sADBC[1]); // 最终结果 str = AC + sADBC[0] + sBD[0]; return str; } // 两个数字串按位加 private static String addition(String ad, String bc) { // 返回的结果 String str = ""; // 两字符串长度要相同 int lenM = Math.max(ad.length(), bc.length()); ad = formatNumber(ad, lenM); bc = formatNumber(bc, lenM); // 按位加,进位存储在temp中 int flag = 0; // 从后往前按位求和 for (int i = lenM - 1; i >= 0; i--) { int t = flag + Integer.parseInt(ad.substring(i, i + 1)) + Integer.parseInt(bc.substring(i, i + 1)); // 如果结果超过9,则进位当前位只保留个位数 if (t > 9) { flag = 1; t = t - 10; } else { flag = 0; } // 拼接结果字符串 str = "" + t + str; } if (flag != 0) { str = "" + flag + str; } return str; } // 处理数字串,分离出进位; // String数组第一个为原位数字,第二个为进位 private static String[] dealString(String ac, int len1) { String[] str = {ac, "0"}; if (len1 < ac.length()) { int t = ac.length() - len1; str[0] = ac.substring(t); str[1] = ac.substring(0, t); } else { // 要保证结果的length与入参的len一致,少于则高位补0 String result = str[0]; for (int i = result.length(); i < len1; i++) { result = "0" + result; } str[0] = result; } return str; } // 乘数、被乘数位数对齐 private static String formatNumber(String x, int len) { while (len > x.length()) { x = "0" + x; } return x; } //测试桩 public static void main(String[] args) { // 正则表达式:不以0开头的数字串 String pat = "^[1-9]\\d*$"; Pattern p = Pattern.compile(pat); // 获得乘数A System.out.println("请输入乘数A(不以0开头的正整数):"); Scanner sc = new Scanner(System.in); String A = sc.nextLine(); Matcher m = p.matcher(A); if (!m.matches()) { System.out.println("数字不合法!"); return; } // 获得乘数B System.out.println("请输入乘数B(不以0开头的正整数):"); sc = new Scanner(System.in); String B = sc.nextLine(); m = p.matcher(B); if (!m.matches()) { System.out.println("数字不合法!"); return; } System.out.println(A + " * " + B + " = " + bigIntMultiply(A, B, Math.max(A.length(), B.length()))); } }

浙公网安备 33010602011771号