本文詳細介紹了計算機進制的基本概念,隨後給出了常見進制轉換的方法,然後介紹了整數的二進制的計算規則,最後說明了一些二進制計算需要注意的地方(坑)。
1 進制概述
進制:就是進位制,是人們規定的一種進位方法。對於任何一種進制–X進制,就表示某一位置上的數運算時是逢X進一位。二進制就是逢二進一,八進制是逢八進一,十進制是逢十進一,十六進制是逢十六進一。
二進制的由來:任何數據在計算機中都是以二進制的形式存在的。二進制早期由電信號開關演變而來,1表示開,0表示關。這樣1,0組成的數據就是二進制數據。
1.2 計算機儲存單位
計算機中,一個二進制數佔用一位(bit,也叫比特),八位二進制數組成一個字節(byte)。它們是計算機儲存單位,用來儲存數據。基本儲存單位是bit,常用儲存單位是字節Byte。
常見計算機儲存單位有:
Bit——比特
Byte——字節
KB——千字節
MB——兆字節
GB——吉字節
TB——太字節
計算機儲存單位的換算規則是:
1 Byte(B) = 8 bit
1 Kilo Byte(KB) = 1024B
1 Mega Byte(MB) = 1024 KB
1 Giga Byte (GB)= 1024 MB
1 Tera Byte(TB)= 1024 GB
1 Peta Byte(PB) = 1024 TB
1 Exa Byte(EB) = 1024 PB
1 Zetta Byte(ZB) = 1024 EB
1Yotta Byte(YB)= 1024 ZB
1 Bronto Byte(BB) = 1024 YB
1Nona Byte(NB)=1024 BB
1 Dogga Byte(DB)=1024 NB
1 Corydon Byte(CB)=1024DB
1.3 不同進制的組成
二進制
由0,1組成。以0b開頭
八進制
由0,1,…7組成。以0開頭
十進制
由0,1,…9組成。整數默認是十進制的
十六進制
由0,1,…9,a,b,c,d,e,f(大小寫均可,分別代表10,11,12,13,14,15)。以0x開頭
1.4 簡單的不同進制的整數轉換
係數:就是每一位上的數據。
基數:X進制,基數就是X。
權:在數的右邊,從0開始編號,對應位上的編號即爲該位的權。
1.4.1 其他進制整數到十進制
十進制 --------------------> 十進制
12345 =1 * 104+2*103+3 * 102+4*101+5 * 100 =12345
推論: 其他進制轉換爲十進制,把 係數*基數的權次冪 相加 即可。
練習:
把0b100,0100,0x100轉換成十進制
0b100=1 * 22+0 * 21+0 * 20 = 4
0100=1 * 82+0 * 81+0 * 80= 64
0x100=1 * 162+0 * 161+0 * 160= 256
1.4.2 十進制整數到其他進制
推論:除基(要轉換位某進制的基數)取餘,直到商爲0,餘數反轉。
把52分別表示成二進制,八進制,十六進制
1.4.3 任意x進制轉換爲y進制
方法一:
X進制 —— 十進制 —— y進制
方法二:
二進制到八進制 從右到左,3位組合,高位不足補零,分組轉換爲10進制,再組合
100110 100 - 4 110 -6 ——>046
二進制到十六進制 從右到左,3位組合,高位不足補零,分組轉換爲10進制,再組合
100110 0010 – 2 0110 – 6 ——>0x26
2 有符號數據表示法
Java針對整數常量提供了4種表現形式:二進制 八進制 十進制 十六進制。二進制的操作至少應該有八位。不夠位的在左邊補零,首位是符號位,正數爲0,負數爲1。如沒有指定是負數,則默認是正數。
在計算機內,有符號數有3種表示法:原碼、反碼和補碼。所有數據的運算都是採用補碼進行的。補碼至少爲8位,不夠補0。
原碼:
就是二進制定點表示法,即最高位爲符號位,“0”表示正,“1”表示負,其餘位表示數值的大小。
反碼:
正數的反碼與其原碼相同;負數的反碼是對其原碼逐位取反,但符號位除外。
補碼:
正數的補碼與其原碼相同;負數的補碼是在其反碼的末位加1。
補充:
移碼:不管正負數,將其補碼的符號位取反即可。
案例:使用源碼,反碼和補碼錶示+7和-7
7的二進制爲:111
源碼: 符號位 數值位
+7 0 0000111
-7 1 0000111
反碼:
+7 0 0000111
-7 1 1111000
補碼:
+7 0 0000111
-7 1 1111001
練習:
1.已知某數X的原碼爲10110100B,試求X的補碼和反碼。
源碼 1 0110100
反碼 1 1001011
補碼 1 1001100
2.已知某數X的補碼11101110B,試求其原碼和反碼。
源碼 1 0010010
反碼 1 1101101
補碼 1 1101110
3 整數的二進制運算規則
Java中數的計算,在計算機中都被轉換爲二進制補碼的計算。並且Java中的數都是有符號數。
3.1 加法運算
同十進制的加法運算一樣,滿足2就進一位。例如,計算int類型的 7+6 的值:由於兩個數都是正數,因此源碼、反碼、補碼值是一樣的:
7的源碼、反碼、補碼值爲:0000 …… 0111 這裏省略前面高位的n個0
6的源碼、反碼、補碼值爲:0000 …… 0110 這裏省略前面高位的n個0
然後使用二進制加法,滿足2就進一位:
0000 0111 ——7
0000 0110 ——6
——————————
0000 1101 ——13
0000 1101轉換爲十進制就是13。可以看到運算還是比較簡單的。
3.2 減法運算
實際上減法的實現比加法更加複雜,主要是減法的借位操作相比於加法的進位更加難以表示。cpu的運算器沒有減法運算,減法運算都是轉換爲加法運算來實現的。
例1,計算int類型的 7-6 的值,可以轉換爲7+(-6)的值:
7的源碼、反碼、補碼值爲:0000 …… 0111 這裏省略前面高位的n個0
-6,由於是負數,那麼它的源碼、反碼、補碼值是不一樣的:
源碼:1000 …… 0110 這裏省略前面高位的n個0
反碼:1111 …… 1001 這裏省略前面高位的n個1
補碼:1111 …… 1010 這裏省略前面高位的n個1
0000 …… 0111 ——7
1111 …… 1010 ——-6
————————————
1 0000 …… 0001 ——1
我們看到,這裏最終多進了一個一,計算對於這種情況才與的方法很簡單,那就是捨去比最高位還要高的位數,那麼最終的二進制補碼爲:0000 …… 0001(這裏省略前面高位的n個0),那麼最終的結果轉換爲十進制就是1。
例2,計算int類型的 7-8 的值,可以轉換爲7+(-8)的值:
7的源碼、反碼、補碼值爲:0000 …… 0111 這裏省略前面高位的n個0
-6,由於是負數,那麼它的源碼、反碼、補碼值是不一樣的:
源碼:1000 …… 1000 這裏省略前面高位的n個0
反碼:1111 …… 0111 這裏省略前面高位的n個1
補碼:1111 …… 1000 這裏省略前面高位的n個1
0000 …… 0111 ——7
1111 …… 1000 ——-8
————————————
1111 …… 1111 —— -1
我們看到,這裏最終沒有多進了一個一,那麼最終的二進制補碼爲:1111 …… 1111(這裏省略前面高位的n個0),那麼最終的結果轉換爲十進制就是-1。
3.3 乘法運算
同理,cpu的乘法運算也是轉變爲對應的加法運算的,我們只需要兩步:
- 計算乘數的絕對值的乘積(加法總和)
- 確定最終乘積的符號,這取決於乘數的符號,同號爲證,異號爲負,然後改變符號位的值即可。
例1,計算int類型的 7*6 的值,可以轉換爲7+7+7+7+7+7的值:
7的源碼、反碼、補碼值爲:0000 …… 0111 這裏省略前面高位的n個0
在我們自己進行計算時,實際上也可以採用十進制乘法的思想:
0000 0111 ——7
0000 0110 ——6
————————————
0000 0000
0000 0111
0000 0111
……
0010 1010 ——最終值 42
0010 1010轉換爲十進制就是42,可以看到採用十進制的運算規則還是比較簡單的,但是注意這不是cpu的方法,這只是我們找出來的規律。
3.4 除法運算
我們都知道Java中的除法/,實際上返回的是商的值,那麼除法是怎麼實現的呢。和乘法一樣,實際上除法可以轉換爲減法,不停的用除數去減被除數,直到被除數小於除數時,此時所減的次數就是我們需要的商,此時的被除數就是餘數,商符號的確定和乘法中符號的確定是一樣的而減法有可以轉換爲加法。
所以實際上計算機的加減乘除只需要加法器就能全部實現。
例1,計算int類型的 7/6 的值,可以轉換爲7-6=1,然後除數變成1小於6,此時除法計算完畢,商爲1,餘數爲1
在我們自己進行計算時,實際上也可以採用十進制除法的思想,只不過這裏的商變成了1和0組成。
0000 0111 ——7
0000 0110 ——6
————————
0000 0001 ——最終值 1
0000 0001轉換爲十進制就是1。
3 二進制運算的坑
從上面的運算我們可以知道,轉換爲加法時,可能會用到進位操作,那麼問題就來了,如果兩個數的和超過所屬類型的最大長度,計算機會如何處理呢?
我們來看下面幾個數的運算:
@Test
public void test4() {
System.out.println(Integer.MAX_VALUE + 1);
System.out.println(Integer.MAX_VALUE + (Integer.MAX_VALUE >> 1));
System.out.println(Integer.MAX_VALUE + (Integer.MAX_VALUE >> 1) - Integer.MAX_VALUE);
System.out.println(Integer.MAX_VALUE + 1 - Integer.MAX_VALUE);
}
我們知道int類型佔據4個字節,32位,由於是有符號的數,那麼int類型的取值範圍就是-231——231-1(-2147483648——2147483647)。Integer.MAX_VALUE表示int類型的最大值,即2147483647。
首先看第一個運算,Integer.MAX_VALUE+1,轉換爲二進制加法運算,如下:
01111111 11111111 11111111 11111111 ——2147483647
00000000 00000000 00000000 00000001 ——1
————————————————————————————
10000000 00000000 00000000 00000000 —— -2147483648
我們可以看到最終計算出來的值,由於進位的關係,導致原本的符號位從0變成了1,導致變成了非常大的負數,但是,注意計算機認爲這種計算是合理的,此時計算機會將計算結果從補碼-反碼-源碼-十進制的展示給我們,那麼我們獲得的十進制結果就變成了-2147483648,即int類型的最小值。
然後看第二個運算,這裏右邊的括號裏表示移位運算符,>>表示右移,移位運算符可以轉換爲m/(2^n)的形式,m表示被移位的數,n表示移動的位數。此時第二個運算轉變成了Integer.MAX_VALUE + Integer.MAX_VALUE / 2。後面的運算是除法,得到的值是1073741823。最後就是加法,轉換爲二進制加法運算,如下:
01111111 11111111 11111111 11111111 ——2147483647
00111111 11111111 11111111 11111111 ——1073741823
——————————————————————————
10111111 11111111 11111111 11111110 —— -1073741826
我們可以看到最終計算出來的值,同樣由於符號位的進位,變成了負數。
接下來看了第三個運算,Integer.MAX_VALUE + (Integer.MAX_VALUE >> 1) -Integer.MAX_VALUE。前面的值我們已經計算出來了,因此我們直接計算後面的減法,轉換爲二進制加法運算,如下:
10111111 11111111 11111111 11111110 ——-1073741826
10000000 00000000 00000000 00000001 —— -2147483647
——————————————————————————————
1 00111111 11111111 11111111 11111111 ——捨棄超過長度的位數00111111 11111111 11111111 11111111 ——1073741823
這一次,我們可以看到符號位同樣被改變了,同時,由於符號位都是1,那麼自然繼續向前進位,明顯超出了32位長度,注意,此時計算機的處理很簡單,直接將比符號位更高位數的值捨棄,那麼最終剩下的二進制補碼轉換爲十進制就是1073741823。
接下來看了第四個運算,Integer.MAX_VALUE + 1 - Integer.MAX_VALUE。前面的值我們已經計算出來了,因此我們直接計算後面的減法,轉換爲二進制加法運算,如下:
10000000 00000000 00000000 00000000 —— -2147483648
10000000 00000000 00000000 00000001 —— -2147483647
————————————————————————————————————
1 00000000 00000000 00000000 00000001 ——捨棄超過長度的位數
00000000 00000000 00000000 00000001 ——1
最終計算的出來的十進制值爲1。
從上面的案例中可以看出來,如果規定了一個數的類型,那麼無論怎麼相加,無論加多少,最終的值一定會屬於原本數據類型的範圍之內,就像一個循環,當超過一端的極值時,會從另一端開始,而不能突破它們的邊界!
明白了這些,對於我們的編碼會很有幫助,特別是理解某些數組集合中關於數組容量上限判斷的源碼,比如ArrayList的hugeCapacity方法,當size的值變成Integer.MAX_VALUE時,它再添加元素時,size+1會變成-2147483648。此時即可表示數組長度超過上限(Java數組長度上限爲Integer.MAX_VALUE)。
如果有什麼不懂或者需要交流,可以留言。另外希望點贊、收藏、關注,我將不間斷更新各種Java學習博客!