两个大整数相乘

不用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())));  
    }  
}  

 

posted @ 2017-07-25 16:24  临江仙zhe  阅读(239)  评论(0)    收藏  举报