【算法】高精度算法

为什么会有高精度算法,因为两个特别大的数进行四则运算大概率会超过int,long之类的数据类型,因为没有哪个数据类型是能够包括无穷大的,下面分别从加减乘除进行讲解。

一.高精度加法

大致思路就是把两个要相加的数以字符串的形式进行输入,因为如果用int等可能都表达不出来,然后把字符串的每一位放到数组中,把两个数组从第一位进行逐位相加,大于十就进位,最后反向输出,就完事了。具体代码如下:

​public void add(String a,String b){
        StringBuffer a1 = new StringBuffer(a);
        StringBuffer b1 = new StringBuffer(b);
        int length=a1.length()>b1.length()?a1.length():b1.length();
        int []numa=new int [length];
        int []numb=new int [length];
        //反向存储
        for(int i=0;i<a1.length();i++){
            numa[i]=a1.charAt(a1.length()-i-1)-'0';
        }
        for(int i=0;i<b1.length();i++){
            numb[i]=b1.charAt(b1.length()-i-1)-'0';
        }
        int []num=new int[length+1];
        //相加
        for(int i=0;i<length;i++){
            num[i]=numa[i]+numb[i]+num[i];
          //>=10就进位
            if(num[i]>=10){
                num[i]-=10;
                num[i+1]+=1;
            }
        }
        //判断是否要从最后一位开始输出,因为不确定最后一位是否因为前一位进位而变成1
        if(num[length]>0){
            length++;
        }
        //反向输出
        for(int i=0;i<length;i++){
         System.out.print(num[length-i-1]);
        }
    }

​

二.高精度减法

其实思路跟加法的思路大差不差,但是有些细节还是要注意,首先保证大数减小数,如果是小数减大数,直接把两个数交换,同样的反向存储,然后逐位相减,不够就向后借位,但要注意不要越界,最后反向输出,具体代码如下:

public void subtraction(String a,String b){
        char x='+';
        //让a成为最大数让大数减小数
        if(a.compareTo(b)<0){
          String temp=a;
          a=b;
          b=temp;
          x='-';
        }
        //处理两个数相同的特殊情况
        else if(a.compareTo(b)==0){
            System.out.print(0);
            return;
        }
        //找出最大长度防止越界
        int length=a.length()>b.length()?a.length():b.length();
        int []numa=new int [length];
        int []numb=new int [length];
        for(int i=0;i<a.length();i++){
            numa[i]=a.charAt(a.length()-i-1)-'0';
        }
        for(int i=0;i<b.length();i++){
            numb[i]=b.charAt(b.length()-i-1)-'0';
        }
        int []num=new int[length];
        for(int i=0;i<length;i++){
            if(numa[i]<numb[i]){
                numa[i]+=10;
                if(i+1<length) {
                    numa[i + 1] -= 1;
                }
            }
            num[i]=numa[i]-numb[i];
        }
        //找出指定位置,从不为0的位置开始
        while(length>0&&num[length-1]==0){
            length--;
        }
        System.out.print(x);
        for(int i=0;i<length;i++){
            System.out.print(num[length-i-1]);
        }
    }

三.高精度乘法

这个要分成两种情况来讲,一个是大数(用字符串表示的数)乘小数,一个是大数乘大数

大数乘小数

其实思路还是差不多,先将大数进行反向存储在一个数组中,然后将数组中的数的每一位与小数进行相乘并存储,然后不断进位,最后反向输出。

 public void multiplication(String a,int b){
        int[] num = new int[a.length() + String.valueOf(b).length()];

        for(int i=0;i<a.length();i++){
            //反向存储
            num[i]=a.charAt(a.length()-i-1)-'0';
            //逐位进行相乘并将结果放回
            num[i]=num[i]*b;
        }
        for(int i=0;i<num.length;i++){
            //进位   
            if(num[i]>=10){
                int JinWei=num[i]/10;
                if(i+1<num.length){
                    num[i+1]+=JinWei;
                }
                num[i]=num[i]%10;
            }
        }
        //寻找遍历起点的索引
        int index=0;
        for(int i=num.length-1;i>=0;i--){
            if(num[i]!=0){
                index=i;
                break;
            }
        }
        //反向输出
        for(int j=index;j>=0;j--){
            System.out.print(num[j]);
        }
    }

大数乘大数

先把两个字符串分别反向存储在两个数组中,第一个数组中的第一个数与第二个数组中每一个数进行相乘并存储在一个新数组中,接着第一个数组的第二个分别相乘并于之前的在新数组中的数进行相加,如此循环,但每次相加都要向右对齐后进行相加,最后反向输出

 public void multiplication2(String a,String b){
        int length=a.length()+b.length();
        int []numa=new int [length];
        int []numb=new int [length];
        int []num=new int [length];
        //反向存储
        for(int i=0;i<a.length();i++){
            numa[i]=a.charAt(a.length()-i-1)-'0';
        }
        for(int i=0;i<b.length();i++){
            numb[i]=b.charAt(b.length()-i-1)-'0';
        }
        for(int i=0;i<a.length();i++){
            for(int j=0;j<b.length();j++){
                //所有的数相加都要向右对其,每次都要比上一次多后退一格
                num[i+j]=numa[i]*numb[j]+num[i+j];
                //进位
                if(num[i+j]>=10){
                    int JinWei=num[i+j]/10;
                    num[i+j+1]+=JinWei;
                    num[i+j]=num[i+j]%10;
                }
            }
        }
        //确定反向输出的开始的索引
        int index=0;
        for(int i=num.length-1;i>=0;i--){
            if(num[i]!=0){
                index=i;
                break;
            }
        }
        //反向输出
        for(int j=index;j>=0;j--){
            System.out.print(num[j]);
        }
    }

四.高精度除法

这里要分三种情况来讲解,分别是小数除小数,大数除小数,大数除大数。

小数除小数

首先两个数相除,我们一般会设置保留的小数位数,然后可以先直接输出小数点前的位数以及小数点,然后取余并用一个变量来接收,模仿除法的过程不断乘10在除然后取余,根据想要保留的小数位数来控制循环的次数就行了。

代码如下:

 public void division1(int a, int b, int n) {
        // 先输出结果的整数部分和小数点
        System.out.print(a / b + ".");
        int t = a % b;
        while (n > 0) {
            t *= 10;
            int temp = t / b;
            System.out.print(temp);
            n--;
            t = t % b;
        }
    }

大数除小数

这里结果有两种方式,有余数的和保留几位小数的,下面分别来讲解一下

···保留余数版

首先将大数顺序存储进数组中,模仿除法的方式,定义一个变量来接收每一次的余数,定义一个数组来存储每次的结果,然后找到结果数组的第一个不为0的数组的索引,然后顺序输出结果以及余数。

代码如下:

public void division2(String a,int b){
        int []num=new int [a.length()];
        for(int i=0;i<a.length();i++){
          num[i]=a.charAt(i);
        }
        //创建一个数组来存储结果
        int []result=new int [a.length()];
        int t=0;//余数
        for(int i=0;i<num.length;i++){
           t=t*10+num[i];
            if(t>=b){
                result[i]=t/b;
                t=t%b;
            }
            else{
                result[i]=0;
            }
        }
        //定位出第一个不为0的索引位置并正向输出数组
        int index=0;
        for(int i=0;i<result.length;i++){
            if(result[i]!=0){
                index=i;
                break;
            }
        }
     for(int j=index;j<result.length;j++){
         System.out.print(result[j]);
     }
     //输出余数
        System.out.print("..."+t);
    }

···保留小数版

其实只要上面那个版本会了就很简单了,上面只处理了整数部分,然后只需要拿余数不断乘10,根据要保留的小数位数来控制循环的次数。

代码如下:

 public void division3(String a,int b,int n){
        int []num=new int [a.length()];
        for(int i=0;i<a.length();i++){
            num[i]=a.charAt(i)-'0';
        }
        //创建一个数组来存储结果
        int []result=new int [a.length()];
        int t=0;//余数
        for(int i=0;i<num.length;i++){
            t=t*10+num[i];
            if(t>=b){
                result[i]=t/b;
                t=t%b;
            }
            else{
                result[i]=0;
            }
        }
        //定位出第一个不为0的索引位置并正向输出数组
        int index=0;
        for(int i=0;i<result.length;i++){
            if(result[i]!=0){
                index=i;
                break;
            }
        }
        for(int j=index;j<result.length;j++){
            System.out.print(result[j]);
        }
        System.out.print(".");
        while(n>0){
            t*=10;
            int temp = t / b;
            System.out.print(temp);
            t=t%b;
            n--;
        }
    }

大数除大数

这个应该是高精度算法中最为复杂的了,要结合前面讲的高精度减法和乘法。他的思路主要是把除法看成减法,因为除法本质是减法,举个例子,10/5=10-5-5,所以用被除数不断的减去除数记录减去的次数就行了,当正真用代码来实现还是有点困难的。具体代码如下

 public static void division4(String a, String b) {
        // 将大数分别倒序放入数组中,但是数组的第一个位置要用来记录数的位数
        int[] numa = new int[200];
        numa[0] = a.length();
        int[] numb = new int[200];
        numb[0] = b.length();
        for (int i = 1; i <= numa[0]; i++) {
            numa[i] = a.charAt(numa[0] - i) - '0';
        }
        for (int i = 1; i <= numb[0]; i++) {
            numb[i] = b.charAt(numb[0] - i) - '0';
        }
        // 记录商的数组
        int[] ans = new int[205];
        // 商的最大位数
        ans[0] = numa[0] - numb[0] + 1;
        // 当 a < b(不考虑位数相同的情况)
        if (ans[0] < 0) {
            System.out.print("0");
            return;
        }
        for (int i = ans[0]; i > 0; i--) {
            int[] t = new int[205];
            // 对 b 补 0
            numcopy(t, numb, i - 1);
            while (compare(numa, t)) {
                sub(numa, t);
                ans[i]++;
            }
        }
        boolean leadingZero = true;
        for (int i = ans[0]; i > 0; i--) {
            if (leadingZero && ans[i] == 0) {
                continue; // 去掉前导 0
            }
            leadingZero = false;
            System.out.print(ans[i]);
        }
        if (leadingZero) {
            System.out.print("0");
        }
        //输出余数
        System.out.print("...");
        leadingZero = true;
        for (int i = numa[0]; i > 0; i--) {
            if (leadingZero && numa[i] == 0) {
                continue; // 去掉余数的前导 0
            }
            leadingZero = false;
            System.out.print(numa[i]);
        }
        if (leadingZero) {
            System.out.print("0");
        }
    }

    public static void numcopy(int[] t, int[] b, int n) {
        t[0] = b[0] + n;
        for (int i = n + 1; i <= t[0]; i++) {
            t[i] = b[i - n];
        }
    }

    public static boolean compare(int[] a, int[] b) {
        if (a[0] > b[0]) {
            return true;
        } else if (a[0] < b[0]) {
            return false;
        } else {
            // 因为数组是反向存储,所以从最高位开始比较
            for (int i = a[0]; i >= 1; i--) {
                if (a[i] > b[i]) {
                    return true;
                } else if (a[i] < b[i]) {
                    return false;
                }
            }
        }
        return true;
    }

    public static void sub(int[] a, int[] b) {
        for (int i = 1; i <= a[0]; i++) {
            a[i] -= b[i];
            if (a[i] < 0) {
                a[i] += 10;
                a[i + 1]--;
            }
        }
        // 去除前导 0
        int l = a[0];
        while (l > 1 && a[l] == 0) {
            a[0]--;
            l--;
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String a = sc.nextLine();
        String b = sc.nextLine();
        division4(a, b);
        sc.close();
    }

有点要说明,上面写的所有代码都没有判断输入是否合法,如果想保证代码的健壮性,可以添加上去,用if判断就行了。还有就是我所定义的数组设置长度的时候有点问题,其实只要根据题目来设置最大的长度就行了,我那样有点问题,但我实在不想改了再加上代码运行没有问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值