引言
在java中,整型分为Integer(4 bytes 32位)和Long(8 bytes 64位)两种类型,因此java中一个整数最多只能占64个bits,并且java中不存在无符号数,无论是Integer类型还是Long类型都是有符号数。当我们需要表示一个超过64 bits的数时,就需要用到BigInteger类型,BigInteger是java包提供的一个大数类型,它的原理就是将大数拆分成一个int[]表示,理论上该int[]数组的长度可以无限增大(大数规定int[]长度不超过1^26),该类提供了普通整型具有的所有运算方法,包括加减乘除,与或非等。本文介绍大数的构造和基本计算原理。
主要成员变量
final int signum; //符号位,1表示正数,-1表示负数,0的符号位为0
final int[] mag; //int数组,从index=0开始表示大数的最高位
private int bitCount; //大数的bit个数加1,默认值为0,当需要时才会被初始化
private int bitLength;
private int lowestSetBit;
private int firstNonzeroIntNum;
最重要的两个成员变量:signum表示大数的符号,int数组mag[]表示大数的值,mag[0]表示大数的最高位
构造方法
通过byte[]数组构造
byte[]数组可以理解为整数在内存中的表示,如:
byte[] b = {0x81,0x00,0x00,0x00} // 在内存中即为0b10000001 00000000 00000000 00000000
由byte[]数组转为大数,可以理解为将byte[]数组转为一个占用 byte.length * 8 位的大整型,byte[]是该数的补码
源码如下:
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);
}
if (mag.length >= MAX_MAG_LENGTH) {
checkRange();
}
}
首先判断byte[0]是正数还是负数,如果byte[0]是正数,则大数的符号位signum为1,mag值为byte[]去掉前缀0;
否则大数的符号位为-1,此时mag的值是-val ,举例如下
byte[] = {0b00001000,0b00000000} 最高位是0,表示正数, 转为大数是它本身
signum = 1
mag[] = int[0] = 0b00001000_00000000 -> 4096
//
byte[] = {0b10001000,0b00000000} 最高位是1,表示负数,转为大数是它的负值
signum = -1
mag[] = int[0] = 0b01111000_00000000
Q:符号位存在的必要性?
指定符号位 BigInteger(byte[] bytes, int signum)
当指定符号位时,无论指定1还是-1,mag[]值都是大数本身
位运算
分析之前先看一段关键代码,getInt方法用于获得一个大数中的每一个int的实际值,为什么要这么做?因为在构造的时候,根据符号位对mag[]的值做了转换,而位运算需要对实际值的每一位做运算,所以要把取负的数再取负还原;
/**
* 对于符号位为正的大数,mag[]返回它本身,对于符号位为负的大数,由于在构造的时候对int值取了负,进行位运算要还原
**/
private int getInt(int n) {
if (n < 0)
return 0;
if (n >= mag.length)
return signInt();
int magInt = mag[mag.length-n-1];
return (signum >= 0 ? magInt :
(n <= firstNonzeroIntNum() ? -magInt : ~magInt));
}
再来看这段代码 n <= firstNonzeroIntNum() ? -magInt : ~magInt
考虑当我们对一个数取负时做了什么操作,答案是减一取反,那么对每一个bit来说,从后往前看找到第一个非0的位,该位和它的后面的位实际上是减一取反(-),该位之前做的都是取反操作(~)
根据上述原理,firstNonzeroIntNum()方法会返回mag[]从后往前第一个非0的index,该index左边的数取反,右边的数取负
按位与 and
public BigInteger and(BigInteger val) {
int[] result = new int[Math.max(intLength(), val.intLength())];
for (int i=0; i < result.length; i++)
result[i] = (getInt(result.length-i-1)
& val.getInt(result.length-i-1));
return valueOf(result);
}
按位取反 not
原理是按照符号位signum将mag[]数组的每个值取其实际值,然后对该值取反
public BigInteger not() {
int[] result = new int[intLength()];
for (int i=0; i < result.length; i++)
result[i] = ~getInt(result.length-i-1);
return valueOf(result);
}