[十六]基礎類型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) { java

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

//另外一個numDigits 用來保存須要處理的數字位數 也就是有效長度,好比去掉前導零後的 算法

int cursor = 0, numDigits; 數組

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

 

//若是給定的基數,不在合法範圍內,那麼拋出異常,不會默認處理 ide

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

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

//若是字符串長度爲0 也是一種非法的參數 ui

if (len == 0) this

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 計算、質數測試、素數生成、位操做以及一些其餘操做
相關文章
相關標籤/搜索