高精度运算(大数运算)

文章目录

  • 摘要
  • 大数加法
  • 大数减法
  • 大数乘法
    • 高精度乘以低精度
  • 大数除法
    • 高精度除以低精度
  • 大数阶乘

摘要

高精度运算是指参与运算的数远大于标准数据类型的数,动辄成百上千位的数。所以高精度数又被称为大数

本文主要讲解:大数加法,大数减法,大数乘法,大数除法,大数阶乘。
java的大数类做这一类题很方便,效率高代码短,但是学会高精度算法还是很有必要的。

另外注意,不是数大的题就是高精度题,要注意审题,比如裸快速幂的题,虽然数很大,但是跟高精度不沾边。


蓝桥杯基础算法和常用API集合:https://blog.csdn.net/GD_ONE/article/details/104061907


大数加法

例题:
基础练习 高精度加法

先给出大数类的写法:

import java.util.*;
import java.io.*;
import java.math.*;

public class Main{
	static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
	public static void main(String[] agrs) throws IOException{
		String s = in.readLine();
		BigInteger a = new BigInteger(s);
		String s1 = in.readLine();
		BigInteger b = new BigInteger(s1);
		a = a.add(b);
		out.write(a.toString());
		out.flush();
	}
}

数组模拟:

因为基本数据类型存不下,所以我们只能将两个数按字符串存储,为了方便计算我们可以将每一位都转化成数字并保存在数组中。然后按位相加,模拟我们平常手算加法的步骤

比如说 11 + 8 11+ 8 11+8, 我们要先算各位, 1 + 8 = 9 1+8 = 9 1+8=9,然后算十位, 1 + 0 = 1 1+0 = 1 1+0=1所以答案是 19 19 19

那么如果遇到进位怎么办,比如 12 + 8 , 个 位 是 2 + 8 = 10 12+8, 个位是2+8=10 12+8,2+8=10,需要向十位进1,也就是算十位的时候要多加上1,对于不进位的情况,其实就是算十位的时候多加0,进几就多加几。

为了使代码更简洁我们不用一个变量专门保存进位多少,我们算出个位的和之后,让其对10取模,就得到了个位要保留多少,然后让和除以10,如果不为0,就说明需要进位。

比如:
9 % 10 = 9 , 9 / 10 = 0 9\%10 = 9, 9 / 10 = 0 9%10=99/10=0也就是说,个位保留9, 不进位。
10 % 10 = 0 , 10 / 10 = 1 10\%10 = 0, 10 / 10 = 1 10%10=010/10=1也就是说,个位保留0, 进1。

import java.util.*;
import java.io.*;
import java.math.*;

public class Main{
	static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
	
	public static void main(String[] agrs) throws IOException{
		String s = in.readLine();
		String s1 = in.readLine();
		int lena = s.length();
		int lenb = s1.length();
		// 也可以用静态数组。
		ArrayList<Integer> a = new ArrayList<>();
		ArrayList<Integer> b = new ArrayList<>();
		
		for(int i = lena - 1;  i >= 0; i--) a.add(s.charAt(i) - '0'); // 先对数字进行处理,保存在数组中。
		for(int i = lenb - 1; i >= 0; i--) b.add(s1.charAt(i) - '0');// 因为需要从个位开始加,所以倒序存储。
		
		ArrayList<Integer> c = new ArrayList<>();
		// c = a + b
		
		int r = 0;
		for(int i = 0; i < lena || i < lenb; i++){
			if(i < lena) r += a.get(i);
			if(i < lenb) r += b.get(i);
			c.add(r%10);
			r /= 10;
		}
		// 最后判断一下最高位是否进位。
		if(r != 0) c.add(r);
		for(int i = c.size()-1; i >= 0; i--) out.write(c.get(i) + ""); // 倒序输出。因为write函数的特性,要将结果转化为字符串输出。
		out.flush();
	}
}

主要理解这一段代码:

for(int i = 0; i < lena || i < lenb; i++){
	if(i < lena) r += a.get(i);
	if(i < lenb) r += b.get(i);
	c.add(r%10);
	r /= 10;
}

大数减法

大数类:

import java.io.*;
import java.math.*;

public class Main {
	
	static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
	
	public static void main(String[] args) throws IOException{
		String s = in.readLine();
		String s1 = in.readLine();
		BigInteger a = new BigInteger(s);
		BigInteger b = new BigInteger(s1);
		
		a = a.subtract(b);
		out.write(a.toString());
		out.flush();
	}
}


大数减法呢和大数加法其实大差不大,也是同样的套路,将数字存于数组中,然后按位相减,需要借位的话,算下一位的时候就多减。

不过需要注意的是:除了0以外,数字不能出现前导0,并且无论是大数减小数还是小数减大数,算的时候统一用大的减小的,如果结果是负的,那么算完前面加个负号就行了。

import java.io.*;
import java.math.*;
import java.util.*;
public class Main {
	
	static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
	
	public static void main(String[] args) throws IOException{

		String s = in.readLine();
		String s1 = in.readLine();
		
		int lena = s.length();
		int lenb = s1.length();
		
		int f = 0;
		// 判断谁大谁小。
		if(lena < lenb || lena == lenb && s.compareTo(s1) < 0){
			String t = s;
			s = s1;
			s1 = t;
			
			f = 1;
			
			int t1 = lena;
			lena = lenb;
			lenb = t1;
		}
	
		ArrayList<Integer> a = new ArrayList<>();
		ArrayList<Integer> b = new ArrayList<>();
				
		for(int i = lena - 1;  i >= 0; i--) a.add(s.charAt(i) - '0'); // 先对数字进行处理,保存在数组中。
		for(int i = lenb - 1; i >= 0; i--) b.add(s1.charAt(i) - '0');// 因为需要从个位开始减,所以倒序存储。
			
		ArrayList<Integer> c = new ArrayList<>();
				// c = a + b
				
				
		int r = 0;
		for(int i = 0; i < lena; i++){
			r += a.get(i);
			
			if(i < lenb) 
				r -= b.get(i);
			
			c.add((r+10)%10); // (r+10)%10的含义是, 当r<0, r+10就是借位后的值,如果r>0, r+10后再对10取余仍得到原来的r。
			
			if(r < 0){ // 如果r < 0说明借位了
				r = -1;
			}
			else r = 0; // 否则r是0
			
		}
		if(r != 0) c.add(r);
		
		if(f == 1) out.write("-");// 判断是不是负数。
		
		int flag = 0;
		for(int i = c.size()-1; i >= 0; i--){
			if(flag == 0 && c.get(i) != 0){
				flag = 1;
			}
			if(flag == 1 || i == 0) // 当没有前导0或者答案是0时输出。
				out.write(c.get(i) + ""); // 倒序输出。
		}
		
		out.flush();
	}
}



大数乘法

大数类:

import java.util.*;
import java.io.*;
import java.math.*;


public class Main {
	static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
	
	public static void main(String[] args) throws IOException{
		String s = in.readLine();
		String s1 = in.readLine();
		
		BigInteger a = new BigInteger(s);
		BigInteger b = new BigInteger(s1);
		
		a = a.multiply(b);
		
		out.write(a.toString());
		out.flush();
	}
}


模拟:

对于大数乘法,我们仍模拟手算的过程。
对于 123 ∗ 6 123*6 1236,用6乘以123的每一位,先不直接进位。:

高精度运算(大数运算)_第1张图片
18要进位, 然后保留8。
所以将12+1,得到13, 13也要进位,然后保留3,最后将6+1得到7,所以最终答案就是738.

一个关键的问题是,把按位计算得到的每个乘积存储在答案数组的哪个位置?

对于加法和减法,我们计算哪一位,直接将答案保存在哪一位。
而乘法显然不能这样保存,观察竖式我们得到,对于 3 ∗ 8 3*8 38我们应该将其保存在数组的第0位,对于 2 ∗ 8 2*8 28我们应该将其保存在数组的第1位,对于 1 ∗ 6 1*6 16我们应该将其保存在数组的第2位,所以规律就是相乘两位的位数之和,数组从0开始,所以分别是 0 + 0 , 0 + 1 , 0 + 2 0+0, 0+1, 0+2 0+00+10+2

也就是说对于数A的第 i i i位,数B的第 j j j位, 两者的乘积应该保存在第 i + j i+j i+j位。
即: c[i+j] += a[i] * b[j], 然后考虑进位,如果c[i+j]大于等于10,则c[i+j+1]需要加上c[i+j]/10,c[i+j]则变为[c+j]%10。 因为小于10时对10取余无影响,所以不用判单可直接简写为:

c[i+j] += a[i]*b[j];
c[i+j+1] += c[i+j]/10;
c[i+j] %= 10;

以上这段代码是大数乘法的核心。

代码:

import java.util.*;
import java.io.*;
import java.math.*;


public class Main{
	static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
	static int[] a = new int[1000], b = new int[1000], c = new int[1000];
	public static void main(String[] args) throws IOException{
		
		String s = in.readLine();
		String s1 = in.readLine();	
		
		int lena = s.length();
		int lenb = s1.length();
		
		for(int i = lena - 1, j = 0; i >= 0; i--, j++)	a[j] = s.charAt(i) - '0';
		for(int i = lenb - 1, j = 0; i >= 0; i--, j++)  b[j] = s1.charAt(i) - '0';
		
		for(int i = 0; i < lena; i++)
			for(int j = 0; j < lenb; j++){
				c[i+j] += a[i] * b[j]; 
				c[i+j+1] += c[i+j]/10; // 进位。
				c[i+j] %= 10; // 保留的值
			}
		
		
		int lenc = lena + lenb - 1; // 两数乘积的最大位数为 lena + lenb, 数组下标从0开始,所以最大是lena + lenb - 1
		while(c[lenc] == 0 && lenc > 0) lenc --; // 移除前导0
		
		for(int i = lenc; i >= 0; i--){
			out.write(c[i] + "");
		}
		
		out.flush();
	}
}


高精度乘以低精度

如果是一个大数乘以一个正常大小的数,例如一个1000位的数乘以9,则方法跟以上方法类似,只需要用9乘以大数的每一位就行了。
核心代码:

ArrayList<Integer> c = new ArrayList<>();
int t = 0;
for(int i = 0; i < lena || t!=0; i++){
	if(i < lena) t += a[i] * b; 
	c.push(t%10);
	t/=10;
}

高精度例题:算法提高 P1001


大数除法

大数类:

import java.io.*;
import java.util.*;
import java.math.*;

public class 高精度除法 {
	static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
	
	public static void main(String[] args) throws IOException{
		String s = in.readLine();
		String s1 =in.readLine();
		
		BigInteger a = new BigInteger(s);
		BigInteger b = new BigInteger(s1);
		
		a = a.divide(b);
		//求余数的话可以用divideAndRemainder()
		//用法:BigInteger[] c = a.divideAndRemainder(b);
		//该函数返回一个数组, c[0] 是商, c[1]是余数
		out.write(a.toString());
		out.flush();
	}
}

另外,需要求余数的话可以用divideAndRemainder()
用法:BigInteger[] c = a.divideAndRemainder(b);
该函数返回一个数组, c[0] 是商, c[1]是余数

需要保留小数可以用BigDecimal

例如:

BigDecimal a, b, c;
a = BigDecimal.valueOf(1.51);
b = BigDecimal.valueOf(1.37);
c = a.divide(b,100,BigDecimal.ROUND_DOWN);//采用向0舍入并并保留100位小数
System.out.println(c);

具体参见:算法竞赛中的常用JAVA API :大数类


高精度除以低精度

因为高精度除以高精度有些麻烦,这里只给出高精度除以低精度的模拟代码:(毕竟java选手考试的时候肯定不会手写高精度

高精度除以低精度同样也是模拟手算的过程。

对于 128 / 8 128/8 128/8
高精度运算(大数运算)_第2张图片
然后:

高精度运算(大数运算)_第3张图片

import java.io.*;
import java.util.*;
import java.math.*;

public class Main {
	static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
	
	static int[] a = new int[100000], c = new int[100000];
	
	public static void main(String[] args) throws IOException{
		String s = in.readLine();
		String s1 =in.readLine();
		
		int lena = s.length();
		int b = Integer.parseInt(s1);
		
		//除法虽然是从高位开始除,但是为了和前面的加减乘保持一致,除法仍从低位开始存。
		for(int i = lena - 1, j = 0; i >= 0; i--, j++) a[j] = s.charAt(i) - '0';
		
		
		int r = 0;
		//从最高位开始除,所以从lena-1开始。
		for(int i = lena - 1, j = 0; i >= 0; i--, j++){
			r = r*10 + a[i];
			c[j] = r / b;
			r = r % b;
		}
		
		// 去除前导0
		int flag = 0;
		for(int i = 0; i < lena; i++){
			if(flag == 0 && c[i] != 0){
				flag = 1;
			}
			if(flag == 1 || i == lena - 1){
				out.write(c[i] + ""); 
			}
		}
		
		out.write("\n" + r);
		out.flush();
	}
}


高精度除法例题:历届试题 小数第n位

代码:

import java.io.*;
import java.util.*;

public class Main {
	static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
	
	public static void main(String[] args) throws Exception{
		int a, b, n;
		String[] s = in.readLine().split(" ");
		a = Integer.parseInt(s[0]);
		b = Integer.parseInt(s[1]);
		n = Integer.parseInt(s[2]);
	
		int i = 0; // i 表示小数后第几位
		a = a % b; // 直接先求出a/b的余数。
		int r = 0;
		while(i < n + 2){
			a *= 10;  // 补0,
			r = a % b; // 暂时保存余数
			a /= b; //计算小数
			i++;
			if(i >= n){ // 如果到了第n位则直接输出
				out.write(a + "");
			}
			a = r;
		}
		out.flush();
	}
}

大数阶乘

大数阶乘本质上还是高精度乘以低精度。

大数类:

	
import java.util.*;
import java.io.*;
import java.math.*;


public class 大数阶乘 {
	static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));

	public static void main(String[] args) throws IOException{
		String s = in.readLine();
		
		int n = Integer.parseInt(s);
		BigInteger b = BigInteger.ONE;
		
		for(int i = 1; i <= n; i++){
			b = b.multiply(BigInteger.valueOf(i));
		}
		
		out.write(b.toString());
		out.flush();
	}
}

模拟:

import java.util.*;
import java.io.*;
import java.math.*;


public class 大数阶乘 {
	static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
	
	// 高精度乘以低精度
	public static void mul(ArrayList<Integer> a, int b, ArrayList<Integer> c) throws IOException{ // c = a*b 
		int lena = a.size();
		ArrayList<Integer> temp = new ArrayList<>();//暂时保存结果。 避免a和c是同一个数组时同时改变a和c
		
		int t = 0; 
		
		for(int i = lena - 1; i >= 0 || t != 0; i--){
			if(i >= 0) t += a.get(i) * b;
			temp.add(t % 10);
			t /= 10;
		}
		
		c.clear();// 先将c数组清空,然后直接将temp的每一位加到c中。
		for(int i = temp.size()-1; i >= 0; i--){ // 这里不使用 temp.clone()的原因是,clone函数是浅拷贝
			c.add(temp.get(i));                  // 而temp是局部变量,当此函数结束时,temp会被销毁,之后res的地址仍是原来的地址。
		}                                       // 也就是说res不会改变。

	}
	
	public static void main(String[] args) throws IOException{
		String s = in.readLine();
		int n = Integer.parseInt(s);
		
		ArrayList<Integer> res = new ArrayList<>();
		res.add(1);// 先赋值1
		
		for(int i = 1; i <= n; i++){//计算阶乘
			mul(res, i, res);  // 这里将其写为函数的形式。
		}
		
		for(int j = 0; j < res.size(); j++){
			out.write(res.get(j) + "");
		}
		
		out.flush();
	}
}

大数阶乘例题:基础练习 阶乘计算

你可能感兴趣的:(#,高精度,蓝桥杯)