BigInteger原理

簡介

big-endian和little-endian

big-endian、little-endian跟多字節類型的數據有關,比如int,short,long型,而對單字節數據byte卻沒有影響。big-endian就是低位字節排放在內存的高地址(右邊),高位字節排放在內存的低地址(左邊)。而little-endian正好相反。
比如 int a = 0x05060708,在big-endian的情況下存放爲:
地址     0 1 2 3
數據     05 06 07 08
在LITTLE-ENDIAN的情況下存放爲:
地址     0 1 2 3
數據     08 07 06 05

BigInteger

先看一下JDK對BigInteger類的介紹:
Immutable arbitrary-precision integers. All operations behave as if BigIntegers were represented in two's-complement notation (like Java's primitive integer types). 
BigInteger provides analogues to all of Java's primitive integer operators, and all relevant methods from java.lang.Math. Additionally, BigInteger provides operations for modular arithmetic, GCD calculation, primality testing, prime generation, bit manipulation, and a few other miscellaneous operations.
上面這段話的意思是:
BigInteger是不可變的任意精度的整數。所有操作中,都以二進制補碼形式表示 BigInteger(如 Java 的基本整數類型)。BigInteger 提供所有 Java 的基本整數操作符的對應物,並提供 java.lang.Math 的所有相關方法。另外,BigInteger 還提供以下運算:模算術、GCD 計算、素數生成、位操作以及一些其他操作。
下面看看BigInteger有哪些重點的屬性,主要的有下面三個:
(1)final int signum
signum屬性是爲了區分:正負數和0的標誌位,JDK註釋裏面已經說的很明白了,-1代表負數,0代表0,1代表整數:
The signum of this BigInteger: -1 for negative, 0 for zero, or 1 for positive. Note that the BigInteger zero must have a signum of 0. This is necessary to ensures that there is exactly one representation for each BigInteger value.
(2)final int[] mag
mag是magnitude的縮寫形式,mag表示的是正數的原碼字節數組。mag數組是存儲BigInteger數值大小的,採用big-endian的順序,也就是高位字節存入低地址,低位字節存入高地址,依次排列的方式。JDK原文註釋如下:
The magnitude of this BigInteger, in big-endian order: the zeroth element of this array is the most-significant int of the magnitude. The magnitude must be "minimal" in that the most-significant int (mag[0]) must be non-zero. This is necessary to ensure that there is exactly one representation for each BigInteger value. Note that this implies that the BigInteger zero has a zero-length mag array.
(3)final static long LONG_MASK = 0xffffffffL;
This mask is used to obtain the value of an int as if it were unsigned。


構造函數

BigInteger(byte[] val)

這個構造方法比較難以理解,網上又找不到資料,所以我就自己看了源碼。
public BigInteger(byte[] val) {
	if (val.length == 0)
	    throw new NumberFormatException("Zero length BigInteger");

	if (val[0] < 0) {
            mag = makePositive(val);
	    signum = -1;
	} else {
	    mag = stripLeadingZeroBytes(val);
	    signum = (mag.length == 0 ? 0 : 1);
	}
    }
一、如果第一個字節是負數,則這個byte[] val就是負數的補碼。因此通過補碼的逆運算(補碼的補碼)可以得到負數的絕對值,再將符號位設置爲-,則得到這個補碼所代表的負數。下面是源碼。
private static int[] makePositive(byte a[]) {
	int keep, k;
        int byteLength = a.length;

	// Find first non-sign (0xff) byte of input
	for (keep=0; keep<byteLength && a[keep]==-1; keep++)//keep表示第一個非符號位也就是-1的字節索引,keep索引開始的字節是有效字節
	    ;

        //k用於判斷,從非符號位開始後面的字節是否全是0,如果是,則表示這個值是補碼所能表示的最小值。固定二進制位數下,補碼錶示的數值範圍是負數最小值的絕對值比正數最大值大1,因此如果要表示負數最小值的絕對值,就要多加1位二進制位,在這裏就表現爲多加一個byte。
	/* Allocate output array.  If all non-sign bytes are 0x00, we must
	 * allocate space for one extra output byte. */
	for (k=keep; k<byteLength && a[k]==0; k++)
	    ;

	int extraByte = (k==byteLength) ? 1 : 0;
        int intLength = ((byteLength - keep + extraByte) + 3)/4;//result數組的長度取決於有效字節的個數和extraByte
	int result[] = new int[intLength];//要返回的結果數組

	/* Copy one's complement of input into output, leaving extra
	 * byte (if it exists) == 0x00 */
        int b = byteLength - 1;
        for (int i = intLength-1; i >= 0; i--) {
            result[i] = a[b--] & 0xff;
            int numBytesToTransfer = Math.min(3, b-keep+1);//numBytesToTransfer表示接下來有幾個字節需要填充到result數組裏,一個int4個字節,所以最大取3
            if (numBytesToTransfer < 0)
                numBytesToTransfer = 0;
            for (int j=8; j <= 8*numBytesToTransfer; j += 8)
                result[i] |= ((a[b--] & 0xff) << j);//每個字節佔8位,所以每次遞加8位

            // Mask indicates which bits must be complemented//mask用於標記哪幾個字節需要取反(one's complement)
            int mask = -1 >>> (8*(3-numBytesToTransfer));
            result[i] = ~result[i] & mask;
        }

	// Add one to one's complement to generate two's complement//補碼取反後,result數組最大索引處的值加1,得到原碼(也就是這個負數補碼所對應的整數)
	for (int i=result.length-1; i>=0; i--) {
            result[i] = (int)((result[i] & LONG_MASK) + 1);
	    if (result[i] != 0)//result[i]此時已經是原碼了,如果原碼全是0,補碼也必定全身0。補碼全0取反後是全1,加1後要進位,因此result[i+1]要加1
                break;
        }

	return result;
    }
總結:如果參數字節數組以-1開頭,不管幾個,只要-1是連續的,那麼這些-1都看成是符號-,這些-1的下一個字節纔是有效字節。如果不以-1開頭而是其他負數,則有效字節從索引0開始。
二、如果第一個字節是正數,則採用stripLeadingZeroBytes方法,將每個字節的二進制補碼按順序連接起來後去掉開頭的0後返回。
/**
     * Returns a copy of the input array stripped of any leading zero bytes.
     */
    private static int[] stripLeadingZeroBytes(byte a[]) {
        int byteLength = a.length;
	int keep;

	// Find first nonzero byte
	for (keep=0; keep<byteLength && a[keep]==0; keep++)
	    ;

	// Allocate new array and copy relevant part of input array
        int intLength = ((byteLength - keep) + 3) >>> 2;
	int[] result = new int[intLength];
        int b = byteLength - 1;
        for (int i = intLength-1; i >= 0; i--) {
            result[i] = a[b--] & 0xff;
            int bytesRemaining = b - keep + 1;
            int bytesToTransfer = Math.min(3, bytesRemaining);
            for (int j=8; j <= (bytesToTransfer << 3); j += 8)
                result[i] |= ((a[b--] & 0xff) << j);
        }
        return result;
    }

BigInteger(int signum, byte[] magnitude)

public BigInteger(int signum, byte[] magnitude) {
	this.mag = stripLeadingZeroBytes(magnitude);

	if (signum < -1 || signum > 1)
	    throw(new NumberFormatException("Invalid signum value"));

	if (this.mag.length==0) {
	    this.signum = 0;
	} else {
	    if (signum == 0)
		throw(new NumberFormatException("signum-magnitude mismatch"));
	    this.signum = signum;
	}
    }
這個構造方法很好理解,mag數組也是採用stripLeadingZeroBytes方法,將每個字節的二進制補碼按順序連接起來後去掉開頭的0後返回,只是符號位可以通過傳參賦予而已。

BigInteger(String val)

將 BigInteger 的十進制字符串表示形式轉換爲 BigInteger。該字符串表示形式包括一個可選的減號,後跟一個或多個十進制數字序列。字符到數字的映射由 Character.digit 提供。該字符串不能包含任何其他字符(例如,空格)。

BigInteger(String val, int radix)

將指定基數的 BigInteger 的字符串表示形式轉換爲 BigInteger。該字符串表示形式包括一個可選的減號,後跟一個或多個指定基數的數字。字符到數字的映射由 Character.digit 提供。該字符串不能包含任何其他字符(例如,空格)。

BigInteger(int numBits, Random rnd)

構造一個隨機生成的 BigInteger,它是在 0 到 (2^numBits- 1)(包括)範圍內均勻分佈的值。該分佈的均勻性假定 rnd 中提供了一個隨機位的公平源 (fair source)。注意,此構造方法始終構造一個非負 BigInteger。

BigInteger(int bitLength, int certainty, Random rnd)

構造一個隨機生成的正 BigInteger,它可能是一個具有指定 bitLength 的素數。相對於此構造方法,建議優先使用 probablePrime 方法,必須指定一個確定數的情況除外。certainty表示調用方允許的不確定性的度量。新的 BigInteger 表示素數的概率超出 (1 - 1/2^certainty)。此構造方法的執行時間與此參數的值是成比例的。


注意事項

BigInteger如何存儲大數據的?

從上面的介紹,我們已經知道了BigInteger的數據存儲情況,它裏面是通過int數組存放的。這些int數組裏的int數值本質上也是通過二進制數位來實現的。int的最大值爲:2147483647。也就是mag[]裏面可以放入2147483647個int值,每個int值爲32位,mag[]可以表示的數組範圍爲:[-2^(2147483647*32-1) ,2^(2147483647*32-1)-1]。

public static BigInteger valueOf(long val)

這個方法可以返回BigInteger,如果值在-16-16之間,那麼返回的BigInteger是同一個對象。以下是源碼:
 public static BigInteger valueOf(long val) {
	// If -MAX_CONSTANT < val < MAX_CONSTANT, return stashed constant
	if (val == 0)
	    return ZERO;
	if (val > 0 && val <= MAX_CONSTANT)
	    return posConst[(int) val];
	else if (val < 0 && val >= -MAX_CONSTANT)
	    return negConst[(int) -val];

	return new BigInteger(val);
    }
    /**
     * Initialize static constant array when class is loaded.
     */
    private final static int MAX_CONSTANT = 16;
    private static BigInteger posConst[] = new BigInteger[MAX_CONSTANT+1];
    private static BigInteger negConst[] = new BigInteger[MAX_CONSTANT+1];
    static {
	for (int i = 1; i <= MAX_CONSTANT; i++) {
	    int[] magnitude = new int[1];
	    magnitude[0] = i;
	    posConst[i] = new BigInteger(magnitude,  1);
	    negConst[i] = new BigInteger(magnitude, -1);
	}
    }

System.out.println(BigInteger.valueOf(16) == BigInteger.valueOf(16));//true
System.out.println(BigInteger.valueOf(17) == BigInteger.valueOf(17));//false
System.out.println(BigInteger.valueOf(-16) == BigInteger.valueOf(-16));//true
System.out.println(BigInteger.valueOf(-17) == BigInteger.valueOf(-17));//false

基本運算示例

public static void main(String[] args) {
        BigInteger b1 = BigInteger.valueOf(20l);
        BigInteger b2 = BigInteger.valueOf(10l);
        System.out.println(b1.add(b2).toString());//加 30
        System.out.println(b1.subtract(b2).toString());//減 10
        System.out.println(b1.multiply(b2).toString());//乘 200
        System.out.println(b1.divide(b2).toString());//除 2
        System.out.println(b1.remainder(b2).toString());//取餘 0
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章