算法之大数相乘

1. 大数相乘问题

计算:999999999999999999999999999999 x 999999999999999999999999999999 = ?(30个9乘以30个9)

在JAVA语言中表示数字的类型有byte、short、int、long,这些类型都有其表示的数据范围,超过了能表示的范围就会产生数据溢出,比如:

// 输出int能表示的最大数加1后的数
System.out.println(Integer.MAX_VALUE + 1);

int能表示的最大数是2147483647,但是这个最大数加上1以后的结果却是-2147483648,并不是2147483648,数据产生了溢出。计算30个9乘以30个9无疑结果是一个很大的数,无论是int还是long都无法装下这个结果。

编程思想中很重要的一点就是学会如何把一个复杂的问题拆解成一堆简单问题的集合,当这个集合中的小问题都解决了那么这个复杂的问题也就得到了解决。

复杂问题的结果 = 简单问题1的结果 + 简单问题2的结果 + … + 简单问题3的结果

2. 大数表示方式

前人的经验都是后来者的宝藏,最早人们通过结绳记事,当要记录的事情越来越多的时候,绳子的结也越来越多,一个结表示一件事,一万件事就得用一万个结来记录,这种记录工作变得极其繁琐。人们开始想到了新加一条特殊的绳子来记录,这条特殊的绳子的一个结表示十件事情,原本需要用一万个结来记录的事现在只需要在这个特殊的绳子上系上一千个结来表示。就像是1这个数值放在个位表示的是一,1这个数值一旦放在十位表示的是十个一,数值开始演变成数字,变得意义非凡。

30个9这个超级大的数超出了int和long等整型的数据范围,我们需要寻找到一个更大的容器来承载这样大的一个数,这样的容器在JAVA中有很多,比如字符串

String number = "99999999999999999999999999999999999999999999999999999999999";

比如数组

int[] numberArray = new int[]{9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9};

只是每个位置的9这个数值它有不同的含义,9在个位即为九个一,9在十位即为九十个一。基于这样的想法,我们可以这要来表示30个9这样的一个超级大数:

有一个30个单元的一维数组,就像算盘一样,每根柱子表示不同的位数,每个单元表示不同的位数,第0号单元表示个位,第1号单元表示十位,依次类推:

在这里插入图片描述

3. 大数相乘计算

还记得在刚开始学习数学时怎么去计算一个两位数乘以两位数的问题吗?我们通过列竖式计算来将一个两位数乘以两位数转化成四个一位数两两相乘然后去进位来得到最终的结果。

在这里插入图片描述

99 x 99可以看成四步相乘

第一步:取第二个99的个位9乘以第一个99的个位9得到结果为81,只不过这个81的1表示个位的一,8表示十位的八。

第二步:取第二个99的个位9乘以第一个99的十位9得到结果为81,只不过这个81的1表示十位的一,8表示百位的八。

第三步:取第二个99的十位9乘以第一个99的个位9得到结果为81,只不过这个81的1表示十位的一,8表示百位的八。

第四步:取第二个99的十位9乘以第一个99的十位9得到结果为81,只不过这个81的1表示百位的一,8表示千位的八。

最后就是将这四步的结果按位对齐相加进位得到9801就是99 x 99的结果。

一个两位数乘以两位数的乘法最终就是转化为多步一位数乘以一位数来进行解决的。那么基于这样的解决思路,30个9乘以30个9也可以看成是第一个数的每个9乘以第二个数的每个9再求和。

4. 大数相乘代码

public class Reckoner{
    private static final String BIT = "个位";

    private static final String TEN = "十位";

    // 计算两个大数number1乘以number2的结果,返回字符串的表现形式
    public String getResult(String number1, String number2){
        // 计算结果的最大位数,以便初始化数组大小,
        int resultMaxDigits = number1.length() + number2.length();
        int[] resultArray = new int[resultMaxDigits];

        // 将计算数据转化成数组结构
        int[] numberArray1 = converArray(number1);
        int[] numberArray2 = converArray(number2);
        // 取每个位分别相乘后再累加到结果数组中
        for (int i = 0; i < numberArray1.length; i++){
            for (int j = 0; j < numberArray2.length; j++){
                int number = numberArray1[i] * numberArray2[j];
                // 一个一位数字与一个一位数字相乘结果可能是一个一位的数字也可能是一个两位的数字
                if (number >= 10){
                    // 两位的数字需要分离每个位上的值,每个位置上的值有两层含义,一层是多大,一层是位置
                    int ten = getNumber(number, TEN); // 对应结果数组中的第(i + j + 1)个空间单元
                    int bit = getNumber(number, BIT); // 对应结果数组中的第(i + j)个空间单元

                    // 结果数组累加这个两位数字的后一位数字
                    resultArray[i + j] = resultArray[i + j] + bit;
                    // 累加后需要判断这个空间的数字是否满足进位条件
                    resultArray = isDoCarray(resultArray, i + j);

                    // 结果数组累加这个两位数字的后一位数字
                    resultArray[i + j + 1] = resultArray[i + j + 1] + ten;
                    // 累加后需要判断这个空间的数字是否满足进位条件
                    resultArray = isDoCarray(resultArray, i + j + 1);
                }else{
                    // 一个一位的数字只需要累加一次再去进位
                    resultArray[i + j] = resultArray[i + j] + number;
                    resultArray = isDoCarray(resultArray, i + j);
                }

            }
        }
        // 把这个数组转化成字符串形式返回,这里需要倒序循环,因为0号索引表示个位
        String result = "";
        for (int i = resultArray.length - 1; i >= 0 ; i--){
            result += resultArray[i];
        }
        // 结果有可能类似于0980125这种以0开头的情况,需要去掉开头的0字符
        return isDoZero(result);
    }

    /**
     * 数据转化为数组结构,数组的第一个位置表示个位,第二个位置表示十位,依次类推
     */
    private int[] converArray(String number){
        char[] chars = number.toCharArray();
        int[] result = new int[chars.length];
        for (int i = chars.length - 1; i >= 0; i--){
            result[result.length - 1 - i] = Integer.parseInt(chars[i] + "");
        }
        return result;
    }

    /**
     * 分离一个十位数字的每个位的值
     */
    private Integer getNumber(Integer number, String digits){
        if (BIT.equals(digits)){
            // 获取数字的个位数值
            return number % 10;
        }else if (TEN.equals(digits)){
            // 获取数字的十位数值
            return number / 10;
        }
        return null;
    }

    /**
     * 是否需要进位,index位置的数大于10需要进位
     */
    private int[] isDoCarray(int[] array, int index){
        if (array[index] >= 10){
            array[index + 1] = array[index + 1] + 1;
            array[index] = getNumber(array[index], BIT);
            // 递归进位,index位向(index + 1)位进位可能导致(index + 1)位需要进位
            isDoCarray(array, index + 1);
        }
        return array;
    }

    /**
     * 递归去掉字符串开头的0
     */
    private String isDoZero(String content){
        // 如果content长度为1并且内容为"0"那么说明大数相乘结果为0
        if (content.length() != 1 && content.startsWith("0")){
            return isDoZero(content.substring(1));
        }
        return content;
    }
}
  • 测试代码
String number = "99999999999999999999999999999999999999999999999999999999999";
String result = new Reckoner().getResult(number, number);
System.out.println(result);
  • 测试结果

在这里插入图片描述

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值