BigInteger

有空發表於2024-11-14

平時在儲存整數的時候, Java 中預設是 int 型別, int 型別有取值範圍: -2147483648 ~ 2147483647. 如果數字過大, 我們可以使用 long 型別, 但是如果 long 型別也表示不下怎麼辦呢?

long 型別最大可以儲存的數字 (二進位制形式):

BigInteger
圖1

轉為十進位制形式為:

BigInteger
圖2

就需要用到 BigInteger, 可以理解為非常非常大的整數.

​有多大呢? 理論上最大到 42 億的 21 億次方, 基本上在記憶體撐爆之前, 都無法達到這個上限.

BigInteger 能表示的範圍很大, 從很小的數字如 1, 2, -1, -2, 到很大很大的數字, 只要是整數, 都可以表示.

BigInteger 類的常用的成員方法:

BigInteger
圖3

獲取 BigInteger 的物件有四種方法, 其中前三種為構造方法:

BigInteger
圖4
public BigInteger(int num, Random rnd)       // 獲取隨機大整數, 範圍: [0 ~ 2 的 num 次方 -1]
public BigInteger(String val)                // 獲取指定的大整數, 字串裡面必須是整數, 不能是小數或者 a b 等字母
public BigInteger(String val, int radix)     // 獲取指定進位制的大整數
public static BigInteger valueOf(long val)   // 靜態方法獲取 BigInteger 的物件, 內部有最佳化

BigInteger 物件一旦建立, 內部記錄的值是不能發生改變的.

程式示例:

public class Demo {
    public static void main(String[] args) {
        // 1. 獲取一個隨機的大整數
        Random r = new Random();
        BigInteger bd1 = new BigInteger(4, r);
        System.out.println(bd1);  // [0 ~ 15]
    }
}

程式示例:

public class Demo {
    public static void main(String[] args) {
        // 2. 獲取一個指定的大整數, 可以超出 long 的取值範圍
        // 細節: 字串中必須是整數, 否則會報錯

        // BigInteger bd2 = new BigInteger("1.1");  // NumberFormatException: For input string: "1.1"
        // System.out.println(bd2);

        // BigInteger bd3 = new BigInteger("abc");  // NumberFormatException: For input string: "abc"
        // System.out.println(bd3);
    }
}

程式示例:

public class Demo {
    public static void main(String[] args) {
        // 3. 獲取指定進位制的大整數
        // 細節:
        // 1. 字串中的數字必須是整數
        // 2. 字串中的數字必須要跟進位制吻合. 
        // 比如二進位制中, 那麼只能寫 0 和 1, 寫其他的就報錯. 
        BigInteger bd1 = new BigInteger("100", 10);
        System.out.println(bd1);  // 100
        BigInteger bd2 = new BigInteger("100", 2);
        System.out.println(bd2);  // 4
        // BigInteger bd4 = new BigInteger("123", 2);  // NumberFormatException: For input string: "123" under radix 2
        // System.out.println(bd4);
    }
}

valueOf() 方法只能接受 long 型別的引數, 引數範圍超過了 long 的話, 方法無法接受, 就報錯了.

程式示例:

import java.math.BigInteger;

public class Demo {
    public static void main(String[] args) {
        // 4. 靜態方法獲取 BigInteger 的物件, 內部有最佳化
        // 細節:
        // 1. 能表示範圍比較小, 只能在 long 的取值範圍之內, 如果超出 long 的範圍就不行了. 

        BigInteger bd1 = BigInteger.valueOf(100);
        System.out.println(bd1);  // 100
        // BigInteger bd2 = BigInteger.valueOf(10000000000000000000000000L);  // Long number too large, java: 整數太大
        // System.out.println(bd2);
    }
}

程式示例:

import java.math.BigInteger;

public class Demo1 {
    public static void main(String[] args) {
        // 4. 靜態方法獲取 BigInteger 的物件, 內部有最佳化.
        // 細節:
        // 2. 在內部對常用的數字: -16 ~ 16 進行了最佳化. 
        // 提前把 -16 ~ 16 先建立好 BigInteger 的物件, 如果多次獲取不會重新建立新的. 
        BigInteger bd5 = BigInteger.valueOf(16);
        BigInteger bd6 = BigInteger.valueOf(16);
        System.out.println(bd5 == bd6); // true

        BigInteger bd7 = BigInteger.valueOf(17);
        BigInteger bd8 = BigInteger.valueOf(17);
        System.out.println(bd7 == bd8); // false
    }
}

檢視 BigInteger 的原始碼:

儲存 -16 ~ 16 這些 BigInteger 物件的方式:

BigInteger
圖4

0 這個 BigInteger 物件:

BigInteger
圖5

valueOf() 方法:

BigInteger
圖6

程式示例:

public class Demo1 {
    public static void main(String[] args) {
        // 5. 物件一旦建立內部的資料不能發生改變
        BigInteger bd9 = BigInteger.valueOf(1);
        BigInteger bd10 = BigInteger.valueOf(2);
        // 此時, 不會修改參與計算的 BigInteger 物件中的值, 而是產生了一個新的 BigInteger 物件記錄結果 3
        BigInteger result = bd9.add(bd10);
        System.out.println(result);  // 3

        System.out.println(bd9 == result);  // false
        System.out.println(bd10 == result);  // false
    }
}

程式示例:

import java.math.BigInteger;

public class Demo2 {
    public static void main(String[] args) {
        /*
            public BigInteger add(BigInteger val) 加法
            public BigInteger subtract(BigInteger val) 減法
            public BigInteger multiply(BigInteger val) 乘法
            public BigInteger divide(BigInteger val) 除法, 獲取商
            public BigInteger[] divideAndRemainder(BigInteger val) 除法, 獲取商和餘數
            public boolean equals(Object x) 比較是否相同
            public BigInteger pow(int exponent) 次冪
            public BigInteger max/min(BigInteger val) 返回較大值/較小值
            public int intValue(BigInteger val) 轉為int型別整數, 超出範圍資料有誤
        */

        // 1.建立兩個BigInteger物件
        BigInteger bd1 = BigInteger.valueOf(10);
        BigInteger bd2 = BigInteger.valueOf(3);

        // 2.加法
        BigInteger bd3 = bd1.add(bd2);
        System.out.println(bd3);  // 13

        // 3.除法, 獲取商和餘數
        BigInteger[] arr = bd1.divideAndRemainder(bd2);
        System.out.println(arr[0]);  // 3
        System.out.println(arr[1]);  // 1

        // 4.比較是否相同
        BigInteger bd4 = BigInteger.valueOf(10);
        BigInteger bd5 = BigInteger.valueOf(10);
        boolean equal1 = bd4.equals(bd5);
        System.out.println(equal1);  // true

        BigInteger bd6 = BigInteger.valueOf(10);
        BigInteger bd7 = BigInteger.valueOf(1);
        boolean equal2 = bd6.equals(bd7);
        System.out.println(equal2);  // false

        // 5.次冪
        BigInteger bd8 = bd1.pow(2);
        System.out.println(bd8);

        // 6.max
        BigInteger bd9 = bd1.max(bd2);
        System.out.println(bd9);
        System.out.println(bd9 == bd1);  // true
        System.out.println(bd9 == bd2);  // false
        // 說明沒有建立新的 BigInteger 物件, 而是返回了比較大的那個值

        // 7.轉為 int 型別整數, 超出範圍資料有誤
        BigInteger bd10 = BigInteger.valueOf(2147483647L);
        int i1 = bd10.intValue();
        System.out.println(i1);  // 2147483647

        BigInteger bd11 = BigInteger.valueOf(2147483648L);
        int i2 = bd11.intValue();
        System.out.println(i2);  // -2147483648, 出錯了

        BigInteger bd12 = BigInteger.valueOf(200);
        double v = bd12.doubleValue();
        System.out.println(v);  // 200.0
        // 還有 longValue(), floatValue() 等方法
    }
}

BigInteger 底層儲存方式:

對於計算機而言, 其實是沒有資料型別的概念的, 都是 0101010101.

資料型別是程式語言自己規定的.

這是一個超過 long 型別的數字以及它的二進位制補碼:

圖片名稱

進入 BigInteger 的原始碼:

圖片名稱

signum 成員變數表示 BigInteger 表示的數字的符號, 若 signum 是 -1, 則表示數字為負數, 若 signum 是 0, 則表示這個數字是 0, 若 sugnum 是 1, 則表示這個數字是正數.

圖片名稱

mag 這個陣列儲存的就是資料, 由於資料很大, 所以將其拆分, 將一個很大的數拆成很多個小段, 每一個小段都會放到一個陣列當中.

圖片名稱

signum 和 mag 兩種方式相結合, 就能表示大數字.

透過打斷點的方式檢視 BigInteger 物件:

圖片名稱

Java 中, 陣列是有最大長度的, 陣列的最大長度是 int 的最大值: 2147483647.

在真實情況下, 電腦的記憶體一般是扛不住這麼大的陣列的, 即電腦的記憶體無法建立這麼大的陣列.

在此處, mag 陣列是 int 型別的. 因此, 陣列中每一位能表示的數字: -2147483648 ~ 2147483647

於是可以說,

陣列中最多能儲存元素個數: 21 億多

陣列中每一位能表示的數字個數: 42 億多

因此, BigInteger 能表示的最大數字為: 42 億的 21 億次方

實際中絕大部分電腦都沒有這麼大的記憶體, 因此可以認為 BigInteger 型別的數字是無窮大的.