高精度運算(大數運算)

摘要

高精度運算是指參與運算的數遠大於標準數據類型的數,動輒成百上千位的數。所以高精度數又被稱爲大數

本文主要講解:大數加法,大數減法,大數乘法,大數除法,大數階乘。
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+811+ 8, 我們要先算各位,1+8=91+8 = 9,然後算十位,1+0=11+0 = 1所以答案是1919

那麼如果遇到進位怎麼辦,比如12+8,2+8=1012+8, 個位是2+8=10,需要向十位進1,也就是算十位的時候要多加上1,對於不進位的情況,其實就是算十位的時候多加0,進幾就多加幾。

爲了使代碼更簡潔我們不用一個變量專門保存進位多少,我們算出個位的和之後,讓其對10取模,就得到了個位要保留多少,然後讓和除以10,如果不爲0,就說明需要進位。

比如:
9%10=99/10=09\%10 = 9, 9 / 10 = 0也就是說,個位保留9, 不進位。
10%10=010/10=110\%10 = 0, 10 / 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();
	}
}


模擬:

對於大數乘法,我們仍模擬手算的過程。
對於1236123*6,用6乘以123的每一位,先不直接進位。:

在這裏插入圖片描述
18要進位, 然後保留8。
所以將12+1,得到13, 13也要進位,然後保留3,最後將6+1得到7,所以最終答案就是738.

一個關鍵的問題是,把按位計算得到的每個乘積存儲在答案數組的哪個位置?

對於加法和減法,我們計算哪一位,直接將答案保存在哪一位。
而乘法顯然不能這樣保存,觀察豎式我們得到,對於383*8我們應該將其保存在數組的第0位,對於282*8我們應該將其保存在數組的第1位,對於161*6我們應該將其保存在數組的第2位,所以規律就是相乘兩位的位數之和,數組從0開始,所以分別是0+00+10+20+0, 0+1, 0+2

也就是說對於數A的第ii位,數B的第jj位, 兩者的乘積應該保存在第i+ji+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/8128/8
在這裏插入圖片描述
然後:

在這裏插入圖片描述

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

大數階乘例題:基礎練習 階乘計算

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章