[十六]基礎類型BigInteger簡介


 
 
BigInteger和BigDecimal都是Java針對大數提供的類
超出了java的表示范圍
image_5bd81e9b_5f09
 

屬性簡介

借助於signummag 來實現數據的符號位和實際數據的保存
final int signum 保存BigInteger的符號
負數 -1
0 0
正數 1
 
final int[] mag;保存數字的數據
字節序為大端模式,大端模式就是低地址存儲高位
 
數組的第一個元素必須是非0的,也就是如果有前導零將會被移除
這樣可以保證每個數都有一個唯一的表示形式
這種要求下 BigInteger的0有一個0長度的數組保存
對於BigInteger 他的數據打開就是這么一種形式
 
[ 101....32位....1] [ 110....32個....1] ....N個..... [ 0110....32個....1]
 
它的真值的計算方法與其他的二進制序列一樣的
二進制為 0111 1110    的十進制為126 相信誰都會計算,BigInteger也是如此的
 
尤其是對於BigInteger字符串參數的構造形式
千萬不要以為就是把字符的編碼或者字符轉換成數字切段存放到int數組中
他存放的都是轉換后的真值
下面會詳細介紹
 

使用字節數組構造

內部是Int數組,一個int 32位就是 4個字節,所以自然是可以使用字節對BigInteger進行構造的
提供了兩種形式的字節構造方法,可以指定符號的
image_5bd81e9b_4bfe
使用字節進行構造,就是把所有的字節填充到int數組中
不過要注意的是,
計算機中存儲的數值都是補碼的形式
正數的補碼與原碼相同
負數的補碼是他的原碼取反再加一
就是把這些字節的補碼按照順序拼在一起,組合成int數組
  • 如果是一個負數,會先得到真值的絕對值
  • 如果有前導零,還會去掉所有的前導零
而且,是大端排序,大端排序,大端排序的把最終的數據存儲起來
也就是說int數組中保存的都是真值的絕對值,使用專門的符號位記錄正負和0
 

原碼/反碼/補碼

先回顧下原碼/反碼/補碼 的概念
原碼

符號位+數值位
符號位為0 表示正數,符號位為1 表示負數
 
數值位就是真值的絕對值
又被稱為帶符號的絕對值表示
反碼 正數的反碼為其原碼
負數的反碼為其原碼的除符號位外,逐位取反
補碼 正數的補碼為其原碼
負數的補碼為其反碼+1 
 

補碼計算步驟

第一步求原碼: 先寫出來她的原碼--->符號位+數值位(絕對值)
第二步求反碼:
如果是正數 反碼與原碼一樣
如果是負數 反碼為原碼取反(除符號位外,逐位翻轉) 
第三步求補碼:
如果是正數 補碼與原碼一樣
如果是負數 補碼為反碼 + 1
第四步擴充:
如果不足數據類型的寬度,將需要填充到指定寬度
符號位擴充,也就是正數補0  負數補1
總結
不管什么形式,第一位始終都是符號位,0 表示正數, 1表示負數
正數原碼/反碼/補碼 全都一樣,知道一種就直接得到另外的形式
負數如果知道補碼,想要得到他的原碼,只需要對補碼再一次的求補碼即可
 

示例1

image_5bd81e9b_66a8
 

示例2

image_5bd81e9b_24ee
 
通過這兩個例子應該可以看得出來,數值都是補碼形式存放
字節存儲的也是補碼 , int存儲的也是補碼,
所以使用字節構造 就是把所有的補碼拼湊在一起就好了 
拼湊排列好的補碼,如果是正數,那么原碼/反碼/補碼全都一樣,存儲的就是這個值
如果是負數,還需要取他的絕對值,絕對值就是 再求一次補碼,去掉符號位就是絕對值了
BigInteger數組中,存儲的都是真值的絕對值的補碼,真值絕對值得補碼,其實就是原碼去掉符號位嘛,一個意思
就像上面的第二個例子  得到的補碼為:  
1000  0011  1111 0111  0000  0000  0101 1001
實際存儲的是:
0111  1100   0000 1000  1111  1111 1010  0111
 

 

使用String構造

String作為參數的構造方法有兩種形式
本質上只是一種,那就是指定基數的字符串轉換為BigInteger
簡化形式就是默認十進制的形式
image_5bd81e9c_6476
 
通過String構造BigInteger的邏輯比較簡單,但是實現看起來會有些令人模糊
接下來我們先介紹一點相關的計算基礎

算法基礎

int能夠支撐的數據長度以及基數
我們知道,存儲實際數據的是int數組
int表示范圍是:   
-231 ~ 231-1     也就是   -2147483648 ~ 2147483647
對於十進制
可以表示10位十進制數字
但是 2147483648 (2147483647+1)  仍舊是10位數字卻溢出了
所以選擇保存9位十進制數
 
所以每個int   十進制下最大值為10的9次方
image_5bd81e9c_6544
對於二進制
最大值 231-1 ,所以只能保存30位 2進制數
 
所以每個int   二進制下最大值為2的30次方
image_5bd81e9c_595a
對於三進制
319 =1162261467 <2147483647<320 = 3486784401  
所以能夠保存19位 三進制數
 
所以每個int   三進制下最大值為3的19次方
image_5bd81e9c_3b88
對於四進制
415 = 1073741824 < 2147483647 < 416 = 4294967296  
所以能夠保存15位 四進制數
 
所以每個int  四進制下最大值為4的15次方
image_5bd81e9c_33dd
對於十六進制
167 =268435456 < < 2147483647 < 168 =  4294967296
所以能夠保存7位十六進制數

所以每個int  十六進制下最大值為16的7次方
image_5bd81e9c_1fb5
所以就有了這么兩個映射數組
digitsPerInt
表示每個int 可以表示的,指定進制下數字的位數,下標索引就是進制基數
比如可以表示十六進制的位數為digitsPerInt[16] = 7
intRadix
表示每個int可以表示的指定進制下的最大值,下標索引就是進制基數
比如 每一位int  可以表示的十進制的最大值為  intRadix[10] = 0x3b9aca00=1,000,000,000
image_5bd81e9c_957
其實intRadix這個數就是:
BigInteger在這個基數下的基數
這句話有點繞,BigInteger內部是數組,假如為mag[0] mag[1]    intRadix[10] = 0x3b9aca00
那么也就是,BigInteger在十進制,也就是10為基數下的基數為0x3b9aca00
那么這個值就是 mag[0] x 0x3b9aca001   + mag[1] x 0x3b9aca00
0  
就如同十進制的數12的計算方式為1x101 + 2 x100 =12 一樣的道理
下面還會繼續說明
同理它內部也有兩個針對Long 的數組,用於內部計算使用
 
BigInteger內部使用int數組表示
普通數值使用每個數值位上的數字進行表示
一個BigInteger有多個int
一個普通數值有多個數字位

每個int能夠表示的指定進制的最大值--intRadix 中保存的數據
其實 就是 BigInteger 的基於每個int作為一個元素的進制基數
 
image_5bd81e9c_1e0
 
假設R為指定的基數
L為指定基數的數字的長度

那么用多少位2進制數可以表示?

x位二進制能夠表示的最大值為
image_5bd81e9c_125
L位R進制的數能夠表示的最大值為
image_5bd81e9c_6d2b
比如R=10 L=2 也就是十進制兩位數能夠表示的最大值為: 10的平方減1     等於 99
image_5bd81e9d_3e48
解上面的方程,可以得出來
x的長度為 :L    乘以     以2為底R的對數
內部還有一個數組
這個數組的值就是以2為底R的對數的值,然后乘以1024,然后進行向上取整
image_5bd81e9d_534e
bitsPerDigit 就是每個數字需要的比特位數乘以1024后在取整
之所以乘以1024然后在取整
應該是為了簡化運算,這個數必然要是2的N次方,計算機移位最快
當然,這個地方乘以1024 實際使用的時候必然也還得除以1024
image_5bd81e9d_6359
以2為底 2的對數 =  1                        * 1024 = 1024
以2為底 3的對數 = 1.5849625007      * 1024 = 1623.0016007168 -> 1624
以2為底 4的對數 =  2                        * 1024 = 2048
以2為底 5的對數 =   2.3219280949     * 1024 = 2377.6543691776 ->2378
以2為底 10的對數 =  3.3219280949   * 1024=3401.6543691776 -> 3402
以2為底 16的對數 =  4                      * 1024 = 4096
 
說到這,我們再回頭看看上面介紹的幾個數組
 
digitsPerInt  表示不同基數(進制)下一個int 能夠表示的數字的長度 ,這個位數其實就是按照多長進行分割組裝
intRadix  就是基數
bitsPerDigit  是用來推算需要多少個int的,也就是int數組的長度
 
以上是String構造BigInteger的用到的一些基本概念
 
我們以一個最簡單的例子進行演示:
計算字符串 "123"  十進制表示的數值
使用數組mag 來進行存儲每一位數字
顯然需要mag[3] 不要糾結mag類型,此處只是為了示例
1. 找到第一個字符  "1" ,轉換為數字1, 然后保存到mag[3] = 1 (我們此處假定從數組最后開始存放)
2. 找到第二個字符  "2" , 轉換為數字2,然后 計算 mag[3] x 10 +2  
mag[3] x 10 = 10 ,結果進行保存
mag[2] 保存1   mag[3] 保存0  
然后再加上2   0+2 = 2 不用進位
所以最終結果為mag[3] = 2  mag[2] = 1
3. 找到第三個字符  "3" , 轉換為數字3,然后 計算 (mag[2]mag[3]) x 10 +3
mag[2]mag[3]  就相當於是兩位數 比如12
image_5bd81e9d_4d57
此時 mag[3] = 0  mag[2] = 2   mag[0] = 1 
然后還需要加 3
mag[3] + 3 = 0+3 = 3 也沒有進位
那么最終結果為
mag[0] = 1  mag[2] = 2  mag[3] = 3
以上就是一個簡單的從字符串123 轉換為10進制數,並且保存到數據的過程
String的構造就是類似這樣的一個過程
 
 

構造方法源碼解析

我們從構造方法入手,簡單介紹下內部是如何運作的

public BigInteger(String val, int radix) {

//定義了兩個變量一個光標,光標記錄着應該要處理的數據索引下標

//另一個numDigits 用來保存需要處理的數字位數 也就是有效長度,比如去掉前導零后的

int cursor = 0, numDigits;

final int len = val.length();//傳遞進來的字符數組的長度

 

//如果給定的基數,不在合法范圍內,那么拋出異常,不會默認處理

if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)

throw new NumberFormatException("Radix out of range");

//如果字符串長度為0 也是一種非法的參數

if (len == 0)

throw new NumberFormatException("Zero length BigInteger");

// Check for at most one leading sign

int sign = 1;

int index1 = val.lastIndexOf('-');

int index2 = val.lastIndexOf('+');

//符號- + 只能出現一個,而且還必須是第一個位置,否則都不合法

//根據最后一個的索引與0 進行比較,可以簡便的判斷符號位是否合法

if (index1 >= 0) {

if (index1 != 0 || index2 >= 0) {

throw new NumberFormatException("Illegal embedded sign character");

}

sign = -1;

cursor = 1;

} else if (index2 >= 0) {

if (index2 != 0) {

throw new NumberFormatException("Illegal embedded sign character");

}

cursor = 1;

}

//經過前面的判斷,如果有符號位的話,光標的值更新為1 也就是后續不處理符號位

//如果此時光標的值等於字符長度,說明沒有有效數字了,將會拋出異常

if (cursor == len)

throw new NumberFormatException("Zero length BigInteger");

 

// Skip leading zeros and compute number of digits in magnitude

//如果有前導0 ,將會去掉這些,光標的位置也會跟着一起移動

while (cursor < len &&

Character.digit(val.charAt(cursor), radix) == 0) {

cursor++;

}

 

//跳過了所有的0之后就不再有有效數據了,說明他就是個0

//哪怕他原來設置的負數的0 將會變為0 的標記

if (cursor == len) {

signum = 0;

mag = ZERO.mag;

return;

}

 

//記錄實際需要處理的數據長度以及對符號位使用signum進行記錄

numDigits = len - cursor;

signum = sign;

 

// Pre-allocate array of expected size. May be too large but can

// never be too small. Typically exact.

//根據前面的公式計算實際需要的二進制位數 numDigits需要處理的數字的長度

//bitsPerDigit 里面記錄了每個進制1位數需要的二進制位數,但是放大了1024倍,所以還要除以1024 也就是右移10

//真正的值可能是小數個,除以1024之后變成了取整了,然后再加上一,百分百夠用,需要的比特位數保存到numBits

long numBits = ((numDigits * bitsPerDigit[radix]) >>> 10) + 1;

if (numBits + 31 >= (1L << 32)) {

reportOverflow();

}

//numWords 記錄的是實際需要的int類型數據的個數,也就是數組的長度

//右移5位就是除以32 就是計算數組的長度,除法會取整,防止1個不足32位的時候,就會變成0了所以numBits加上31 之后再除以32

int numWords = (int) (numBits + 31) >>> 5;

//此時創建真正的保存數據的int數組了

int[] magnitude = new int[numWords];

 

// Process first (potentially short) digit group

//numDigits 需要處理的數字的個數

//digitsPerInt 保存的是每一個int能夠保存的指定數制下的字符長度

//如果有余數,說明有一個不足最大長度的位數

//如果沒有余數,那么每一組都是剛好能夠保存的最大長度

int firstGroupLen = numDigits % digitsPerInt[radix];

if (firstGroupLen == 0)

firstGroupLen = digitsPerInt[radix];

//第一組數據存放到數組的最后一個

String group = val.substring(cursor, cursor += firstGroupLen);

magnitude[numWords - 1] = Integer.parseInt(group, radix);

if (magnitude[numWords - 1] < 0)

throw new NumberFormatException("Illegal digit");

 

// Process remaining digit groups

int superRadix = intRadix[radix];

int groupVal = 0;

while (cursor < len) {

group = val.substring(cursor, cursor += digitsPerInt[radix]);

groupVal = Integer.parseInt(group, radix);

if (groupVal < 0)

throw new NumberFormatException("Illegal digit");

// 這個方法是用來累計計算的,方法內部寫的很復雜

//其實邏輯很簡單,比如一個數字序列1234,求他表示的值是多少

// ( ( (1*10)+2 )*10+3 )*10 +4 = 1234

//這個方法就是用來計算的,只不過每一個位置是一個int 低32位當做數值 高32位當做進位

destructiveMulAdd(magnitude, superRadix, groupVal);

}

// Required for cases where the array was overallocated.

mag = trustedStripLeadingZeroInts(magnitude);

if (mag.length >= MAX_MAG_LENGTH) {

checkRange();

}

}

 

 

構造方法運行步驟

簡單概括下這個方法:
前面的校驗比較簡單
1. 校驗字符的合法性,並且獲得符號位
2. 經過校驗獲取出來最終需要處理的字符的長度
然后就開始了計算
在正式計算之前,需要處理最高位,按照前面介紹的,能夠表示的指定基數的最多位數進行划分
比如10進制表示9位,那么就是9個字符一組
先判斷是否剛好整數倍? 
如果不是,比如10位,那么這個最高位這一個數字自己一組,剩下的9位一組,將會被放到兩個int中
獲得了最高位之后,就開始正式進行計算
如果還有字符需要處理的話
1. 按照位數進行截取,比如10進制截取9位
2. 截取后轉換為數值,然后destructiveMulAdd  這個方法就是第一個參數的數,乘以第二個參數,然后加上第三個參數
就是這樣一個過程
( ( (1*10)+2 )*10+3 )*10 +4 = 1234
每一次的循環中int數組的值都會發生變化
最終獲得最后的結果
 

字符串構造方法計算示例

image_5bd81e9d_6ab0
 
使用字符串"-12345678986543215678901"  進行構造
我們按照方法的計算步驟走一下這個過程 
-12345678986543215678901
字符串總長度24
負號占1位, 光標移動一個位置 cursor=1
還有23個字符長度需要處理
需要處理的數字個數為
numDigits = len - cursor = 23
需要的二進制位數為
((numDigits * bitsPerDigit[radix]) >>> 10) + 1
(23*3402)/1024 +1 = 76+1 = 77
 
需要的int個數, 也就是數組長度為3
(int) (numBits + 31) >>> 5  (77+31)/32 = 3(3.375) 
十進制可以保存9位數字
23 不是9的倍數,商2 余數5
所以最高5位將會被截取單獨存放
取前面5個數字,也就是12345
12345按照10進制解析為數字,存放到最后一個元素
也就是mag[2] = 12345   光標也跟隨移動
數據顯然沒有處理結束, 進入循環處理, 直到最后結束
第一趟:
先獲得接下來的9個字符 也就是 "678986543" ,然后解析為10進制數 678986543
此時
mag[0] = 0,mag[1] = 0  mag[2] = 12345
進入方法 destructiveMulAdd    destructiveMulAdd(int數組, 基數, 截取到的值)
他會乘以基數然后加上截取到的數字
image_5bd81e9d_439a
高32位進位,低32位作為得數
此時mag[0] 和mag[1] 不用在乘了,因為此時都是0  , mag[1] 加上進位即可
此時
mag[0]=0   mag[1] =2874     mag[2] 1263991296
還需要加上678986543
image_5bd81e9d_1d30
沒有進位
所以第一趟結束之后,最終結果為
mag[0]=0   mag[1] =2874      1942977839
第二趟
獲得接下來的9個字符 也就是 "215678901" ,然后解析為10進制數 215678901
image_5bd81e9d_3de2

低32位 為得數  高32位為計數  
也就是 
得數 -603122176  這是個有符號數
可以使用System.out.println(Integer.valueOf(0xDC0D1600)); 打印出來
進位  452384780
 
如同我們平時計算乘法一樣,還需要計算前一位
 
此時  mag[0]=0   mag[1] =2874    mag[2] =  -603122176 
 
2874 x 10的9次方   =  2874000000000
加上 上一個進位  452384780
結果為 2874452384780
10 1001 1101    0100 0010 1011 0110 1001 1100 0000 1100
所以此時第二位得數為 1119263756(后32位)   
進位 10 1001 1101 (高32位)   669
 
所以此時mag數組為:
mag[0] = 669   mag[1] = 1119263756      mag[2]= -603122176
 
還需要加上最后截取的數值
image_5bd81e9d_41e
 
所以最終的結果為
mag[0]=669   mag[1] =1119263756      mag[2] =   -387443275
 
看起來很繁瑣復雜,好奇害死貓,分析這么多只是為了更好地了解這一過程
如果沒興趣只需要記住BigInteger可以直接把字符串轉換為數值進行存儲就好了
 

其他構造方法

另外還有兩個構造方法
public BigInteger(int bitLength, int certainty, Random rnd) 
          構造一個隨機生成的正 BigInteger,它可能是一個具有指定 bitLength 的素數
public BigInteger(int numBits, Random rnd)
          構造一個隨機生成的 BigInteger,它是在 0 到 (2numBits - 1)(包括)范圍內均勻分布的值
 
 

方法簡介

基礎方法

獲取符號位
signum()
常用數學函數
negate()   取負
abs()   絕對值
pow(int)  求冪
gcd(BigInteger)  最大公約數
min(BigInteger)  最小值
max(BigInteger) 最大值
四則運算與取整求余
add(BigInteger)  加法
subtract(BigInteger) 減法
multiply(BigInteger) 乘法
divide(BigInteger)  除法(取整)
remainder(BigInteger) 求余
divideAndRemainder(BigInteger)  取整和求余 返回的是一個數組
獲取基本類型的值
不同於基本數值類型的包裝類,此處並不是直接強轉的
如果太大intValue 和 longValue 將分別返回低的32位和64位
longValue 和 doubleValue可能會被轉換為無窮
intValue()
longValue()
floatValue()
doubleValue()
數值類型的准確值
longValueExact()
intValueExact()
shortValueExact()
byteValueExact()
所謂准確就是不會舍入或者轉換,因為他們會進行數據長度的校驗
否則將會拋出異常
比如
image_5bd81e9d_6661
位操作相關
and(BigInteger)  與 
or(BigInteger)   或 
not()    非
xor(BigInteger)   異或
andNot(BigInteger)   返回其值為 (this & ~val) 的 BigInteger 等效於 and(val.not())
shiftLeft(int) 左移
shiftRight(int)  右移 
 

取模與求余對比

計算過程相同
對於整型數a,b來說,取模運算或者求余運算的方法都是:
  1. 求 整數商: c = a/b;
  2. 計算模或者余數: r = a - c*b.
求模運算和求余運算在第一步不同
取余運算在取c的值時,向0 方向舍入;
取模運算在計算c的值時,向負無窮方向舍入;
 
因此,求模時結果的符號與b一致,求余時結果的符號與a一致
如果a,b都是正整數的話,求模與求余沒有區別
mod(BigInteger)   
返回其值為 (this mod m) 的 BigInteger,取模不同於 remainder
 
BigInteger modPow(BigInteger exponent,BigInteger m)
image_5bd81e9d_56e2
 
BigInteger modInverse(BigInteger m)
image_5bd81e9d_5cf6
 

bitCount與bitLength

public int bitCount()
返回此 BigInteger 的二進制補碼表示形式中與符號不同的位的數量
特別注意這個方法的含義
不是二進制補碼表示形式的 1 位的數量,而是與符號不同的
bitLength
最小的二進制補碼表示形式的位數,不包括 符號位
對於正 BigInteger,這等於常規二進制表示形式中的位數  就是去掉符號位占用的長度
 

valueOf(long)

valueOf(long)
包裝一個long為BigInteger
BigInteger的valueOf有緩沖的作用
image_5bd81e9d_2ba7
 

equals(Object)

equals(Object)
重寫了equals方法
數據相等 才是相等
image_5bd81e9d_25ba
 

toString hashCode CompareTo

public String toString(int radix) 轉換為指定基數
toString()
hashCode()
compareTo(BigInteger)
小於、等於或大於 時,返回 -1,0,或 1
 

素數相關

是否素數
public boolean isProbablePrime(int certainty)
如果此 BigInteger 可能為素數,則返回 true,如果它一定為合數,則返回 false
如果 certainty <= 0,則返回 true
 
參數:
certainty - 調用方允許的不確定性的度量
如果該調用返回 true,則此 BigInteger 是素數的概率超出 (  1 - 1/(2的certainty次方)   )
此方法的執行時間與此參數的值是成比例的
返回:
如果此 BigInteger 可能為素數,則返回 true,如果它一定為合數,則返回 false
public static BigInteger probablePrime(int bitLength,
                                       Random rnd)
返回有可能是素數的、具有指定長度的正 BigInteger
此方法返回的 BigInteger 是合數的概率不超出 2的-100次方

參數:
bitLength - 返回的 BigInteger 的 bitLength。
rnd - 隨機比特源,用這些隨機比特選擇用來進行質數測試的候選數
nextProbablePrime
public BigInteger nextProbablePrime()
返回大於此 BigInteger 的可能為素數的第一個整數
此方法返回的數是合數的概率不超出 2的-100次方
 

特殊的"位操作"

testBit(int)   計算 (this & (1<<n)) != 0
setBit(int)    計算  this | (1<<n)  
clearBit(int) 計算 this & ~(1<<n)
flipBit(int)    計算 this ^ (1<<n)
getLowestSetBit()
返回此 BigInteger 最右端(最低位)1 比特位的索引
也就是從最右邊開始數找到的第一個1
此字節的右端開始到本字節中最右端 1 之間的 0 比特的位數
如果此 BigInteger 不包含1位,則返回 -1
計算 this==0? -1 : log2(this & -this)
 

toByteArray

public byte[] toByteArray()
BigInteger 內部使用int數組進行數據保存
一個int包含4個byte
BigInteger可以使用byte數組構造
也自然能夠分解成byte數組進行保存
 

總結

要記住,內部的存儲int數組 是final int[] mag;  所以是不可變的
他只是用來表示超出Java范圍內的數值
本身的方法雖然內部細節特殊
但是外部呈現並沒有什么特別的,只不過不能使用平時的+-*/符號,需要使用專門的方法
它提供了BigInteger大數值作為數值的基本運算的對應方法
並且還提供了java.lang.Math 的所有相關方法
另外,BigInteger 還提供以下運算:模算術、GCD 計算、質數測試、素數生成、位操作以及一些其他操作
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM