Java大数BigInteger的原理

引言

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

 

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值