Java大数相乘(不使用BigInteger)

作者:黑衣侠客


一.前言

此次Java大数相乘,完全用算法实现,不使用Java大数计算的关键字方法,算法开始很难理解,不过彻彻底底的把每一步过一遍,感觉算法还是很容易的。

二.算法

我们以123乘以456为例:

java 相乘 java数字相乘_不使用BigInteger

在这里我们将123,和456以字符串的形式存储在String中,然后通过再将String类型转为StringBuffer类型,通过StringBuffer的reverse方法,将123,456倒置,然后存储在char[]数组中,这样就会成为654,123,然后我们知道这两个三位数相乘,得到数的长度一定小于两数长度之和,这也就是我每行画了6个格子的含义,同时我在每个乘数的下端都标记了他们在char[]数组中的存储位置。接下来看最右侧红框里的数18,他是在0位置的数字3和在另一个char数组中0位置的数字6相乘得来的,同样,靠着1815是下标为0的数字3和下标为1的数字5相乘的来的,以此类推…
从以上叙述中,我们可以根据每个结果的下标得出一个规律,可以看出,红框的每一列的下标和都是相等的,因此我们可以得出这段代码:
for(int i =0;i<len1;i++){
    for(int j =0;j<len2;j++){
        result[i+j]+=(int)(num1[i]-'0')*(int)(num2[j]-'0');
        //其中result[]是用来存储红框内数据每一列的和,详情见下图
    }
}

java 相乘 java数字相乘_Java_02

result[]是用来存储18,27,28,13,4(按result的存储顺序),其中后面空的一位默认为0。

现在我们处理进位的问题:

由于我们现在采用的是倒置的,所以18其实是个位,因此,个位保留8将10进位到27里,27变为28,而28为十位,保留8,将20进位到28,28变为30,30为百位,保留0,将30进位到13,13变为16,16在保留6,再次进位,4变为5。代码如下:
int leng = result.length;
//开始取余
for(int i=0;i<leng;i++){
    if(result[i]>=10){
        result[i+1]+=result[i]/10;
        result[i]%=10;
    }
}
目前,大致思路已经差不多了,接下来,该将逆序变为正序了,通过StringBuffer的append方法,将字符串变为正序,但是,仔细看会发现,存进去的是 ‘0’,‘5’,‘6’,‘0’,‘8’,‘8’ ,首位的0,是因为我们开始定义result数组长度为6,但只使用了前5个,第六个位置默认值为0,所以在正序之后自然跑到前面来了,因此在将逆序转成正序传入StringBuffer中时,需要加一个判断,代码如下:
//开始倒置,将result中的数组倒置存入StringBuffer
StringBuffer sb = new StringBuffer();
boolean bool = true;
for(int i=leng-1;i>=0;i--){
    if(result[i]==0&&bool){
        continue;
    }
    sb.append(result[i]);
    bool=false;
}
至此,大数相乘的算法思路,已经讲完了,还有一些判断正负号的方法,在具体代码中写的很清楚,虽然代码不长,但是整体思路理解起来还是挺困难的,不过现在理解了,总比开始时不知从哪下手的感觉要好得多,至少心态不崩了。接下来我会将具体代码写到下面。

三.具体代码

import java.util.Scanner;

public class question_test {
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        String num1 = sc.next();
        String num2 = sc.next();
        String num  = bigNumMultiplication(num1,num2);
        System.out.println(num);
    }
    public static String bigNumMultiplication(String f,String s){
        //获取首字母,判断是否是符号位
        char signA = f.charAt(0);
        char signB = s.charAt(0);
        char sign = '+';
        if(signA=='+'||signA=='-'){
            sign = signA;
            f=f.substring(1);//使f返回一个子字符串,该字符串从原始字符串的第二位开始,一直到结尾结束(substring(索引))
        }
        if(signB=='+'||signB=='-'){
            if(signB==sign){
                sign ='+';
            }else{
                sign ='-';
            }
            s=s.substring(1);
        }
        //将大数翻转并转换成字符数组
        char[] num1 =  new StringBuffer(f).reverse().toString().toCharArray();
        //字符串内容较长的时候,特别是这个字符串是动态拼接的时候,用String可能发生内存不够的错误,这种情况必须用stringbuffer
        char[] num2 =  new StringBuffer(s).reverse().toString().toCharArray();
        //StringBuffer()的reverse方法作用是:使StringBuffer中的内容进行反转
        int len1 = num1.length;
        int len2 = num2.length;
        int [] result = new int[len1+len2];
        //计算最终最大长度
        //开始做乘法
        for(int i =0;i<len1;i++){
            for(int j =0;j<len2;j++){
                result[i+j]+=(int)(num1[i]-'0')*(int)(num2[j]-'0');
                //用a[i]-'0'的作用是:如果直接转a[i]为int型那么只会显示a[i]所对应的ASCLL码值,减去字符0的ASCLL码值,正好为改数字,然后再转int型

            }
        }
        int leng = result.length;
        //开始取余
        for(int i=0;i<leng;i++){
            if(result[i]>=10){
                result[i+1]+=result[i]/10;
                result[i]%=10;
            }
        }
        //开始倒置,将result中的数组倒置存入StringBuffer
        StringBuffer sb = new StringBuffer();
        boolean bool = true;//该字段用于标识是否有前置0,如果是0就不需要打印或者存储下来
        for(int i=leng-1;i>=0;i--){
            if(result[i]==0&&bool){
                continue;
            }
            sb.append(result[i]);
            bool=false;
        }
        if(!sb.toString().equals("")){
            if(sign=='-'){
                sb.insert(0,sign);
            }
        }else{
            sb.append(0);
        }
        return sb.toString();
    }
}
注:大家有什么更好的思路或是想法的,欢迎在下方留言。