Java Integer 進制轉化的實現(附源碼),對模與補碼的理解
1.toBinaryString方法的實現
1 public static String toBinaryString(int i) { 2 return toUnsignedString0(i, 1); 3 } 4 private static String toUnsignedString0(int val, int shift) { 5 // assert shift > 0 && shift <=5 : "Illegal shift value"; 6 int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);7 int chars = Math.max(((mag + (shift - 1)) / shift), 1); 8 char[] buf = new char[chars]; 9 10 formatUnsignedInt(val, shift, buf, 0, chars); 11 12 // Use special constructor which takes over "buf". 13 return new String(buf, true); 14 }
Integer.SIZE
這是用來二進制補碼形式表示 int 值的比特位數。
簡單提下為什麽需要用2進制的補碼來表示呢?
簡單的來說,補碼就是取反加1以方便把減法當作加上帶負號的數進行加法運算。
在計算機系統中,數值一律用補碼進行儲存。 主要原因:使用補碼,可以將符號位和其它位統一處理;同時,減法也可按加法來處理。另外,兩個用補
碼表示的數相加時,如果最高位(符號位)有進位,則進位被舍棄
int是4字節,一共是32位。所以Size是常數為32
Integer.numberOfLeadingZeros(val);
在指定 int 值的二進制補碼表示形式中最高位(最左邊)的 1 位之前,返回零位的數量
那麽 mag的含義就是為了進行運算,排除掉數值的2進制中無關位的影響(int是4字節32位)
chars什麽含義並沒有看太懂 我們先且試一個數
例如9(00000000 00000000 00000000 0000 1001)
mag=4 chars=4暫時還是不明白chars 和shift的用途,後面對比8進制 16進制轉換的代碼 就會發現shift取值為1 3 4就是後文模運算(&運算)的關鍵之一。(2^1=2 2^3=8 2^4=16))
繼續向下讀代碼會發現formatUnsignedInt是實現轉化的核心函數
1 formatUnsignedInt(val, shift, buf, 0, chars); 2 static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) { 3 int charPos = len; 4 int radix = 1 << shift; 5 int mask = radix - 1; 6 do { 7 buf[offset + --charPos] = Integer.digits[val & mask]; 8 val >>>= shift; 9 } while (val != 0 && charPos > 0); 10 11 return charPos; 12 }
對比一下參數的傳遞很容易得到chars對應len 長度
這個計算長度的原理是什麽呢?
mag + (shift - 1)) / shift mag是val二進制的長度,最高位一定為1 我們舉八進制的例子來說明
val在2進制中長為mag因為2^3=8 所以二進制中的3位就相當於8進制中的一位(這和Ip點分十進制又非常類似)
shoft-1則是為了處理10 11 位的情況 10/3=3而實際情況是10位的val該是4位的八進制數
終上:
chars = Math.max(((mag + (shift - 1)) / shift), 1);
radit<<shift等於2的shift次方。也就是radit對應到底是幾進制轉化
mask = radix - 1;就是進制轉化對應的模的大小
需要註意的是這和求補碼的模不一樣 這個模是為了得出val最後幾位 對應的進制的值
例如 8(0000 1000)%7(0000 0111) =1 buf 的最後一位的值就是1
do while(當value不為0並且長度>0時)循環裏做什麽呢?
buf[offset + --charPos]這和高精度運算很有幾分相似,都是從末尾開始寫入值。
通過把10進制的數通過位運算後 一位一位的寫入buf這個數組中來 來實現進制的轉換。
而val & mask 這個運算實際上就是模2運算、模8運算、模16運算( 後文附上對模與補碼理解)。
Integer.digits[]在api中沒有找到介紹,網上搜索也只找到integerDigits[n] integerDigits[n,2]的介紹。結合integerDigits[n,2]的介紹和運行知道Integer.digits[]的作用
例如9模2後為00000000 00000000 00000000 0000 0001 應該是只取出最後一位並轉換成char型 存入buf數組中
最後一個string(buf,true)完成進制的轉化輸出。
文末附上對模的認識
模的概念:把一個計量單位稱之為模或模數。例如在模為16的系統中-10其實和+6帶來的影響是一樣的,即
10和6在模12的系統中互為補數(補碼)。計算機的硬件結構中只有加法器,所以大部分的運算都必須最終轉換為加法。
就采用的模的概念對原碼,反碼進行改進以方便運算,即計算機中的運算都是補碼之間的加法運算。個人認為:模的概念在2進制中就是和按位取反是一個東西。模概念的出現就是為了人直觀的進行反碼計算.
我們知道補碼是反碼加1 例如:-9(10001001)--(11110110)--(111101
11)實際就是-9的模128後的2進制+1,為什麽是模128,計算機的計數器是8位的,第一位是符號位所以8位實際能記得數就是256-2^7=128
a mod b = - (-a mod b) -9模128後為(11110110).
那補碼和模到底什麽關系呢?
模的出現是為了方便計算機對負數直接進行帶上符號位的加法運算。模到底是如何實現這一功能的
在計算機網絡的IP地址的點分十進制轉換中經常會發現
255=2^7+2^7-1;2^7=2^6+2^5…………2^1+2^0+1 2^k=2^(k-1)+2^(k-2)+…………+2^1+2^0+1 //這可以通過叠代法來證明 2^k=(1+1)*2^(k-1)=2^(k-1)+2^(k-1) //用k-1 去替代k即k=k-1 2^(k-1)=2^(k-2)+2^(k-2) 以此類推會有2^k=2^(k-1)+2^(k-2)+…………+2^1+2^1 即原式得證
這跟模與補碼有什麽聯系呢?
假如有一個正數a=flag1*2^(n-1)+flag2*2^(n-2)+ …… +flag(n-1)*2^1+flag(n)*2^0
其中flag取0或者1
1 a=2^n-2^n+a
//正數的原,反,補一致沒有討論的必要
-a=2^n-2^n-a
//替換右邊的一個2^n和a,會有如下等式
2^n-a=2^(n-1)+2^(n-2)+…………+2^1+2^0+1-(flag1*2^(n-1)+flag2*2^(n-2)+ …… +flag(n-1)*2^1+flag(n)*2^0)
2^n-a=(1-flag1)*2^(n-1)+(1-flag2)*2^(n-2)+…………+(1-flagn)*2^0+1
(1-flag1)*2^(n-1)+(1-flag2)*2^(n-2)+…………右邊這一行:由於flag是只能取0.1實際這就是按位取反最後+1
2^n-a:2的n次方就是模這點上文已經提到
終上所述:模-原碼=反碼
對於第一符號位的處理和把符號位當作數值位進行運算的理解尚且不足 第一篇博客 水平有限 諸君見諒
Java Integer 進制轉化的實現(附源碼),對模與補碼的理解