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