Java編程的邏輯 (4) - 整數的二進制表示與位運算


本系列文章經補充和完善,已修訂整理成書《Java編程的邏輯》,由機械工業出版社華章分社出版,於2018年1月上市熱銷,讀者好評如潮!各大網店和書店有售,歡迎購買,京東自營鏈接http://item.jd.com/12299018.html


上節我們提到正整數相乘的結果居然出現了負數,要理解這個行為,我們需要看下整數在計算機內部的二進制表示。

十進制

要理解整數的二進制,我們先來看下熟悉的十進制。十進制是如此的熟悉,我們可能已忽略了它的含義。比如123,我們不假思索就知道它的值是多少。

但其實123表示的1*(10^2) + 2*(10^1) + 3*(10^0),(10^2表示10的二次方),它表示的是各個位置數字含義之和,每個位置的數字含義與位置有關,從右向左,第一位乘以10的0次方, 即1,第二位乘以10的1次方,即10,第三位乘以10的2次方,即100,依次類推。

換句話說,每個位置都有一個位權,從右到左,第一位為1,然后依次乘以10,即第二位為10,第三位為100,依次類推。

正整數的二進制表示

正整數的二進制表示與此類似, 只是在十進制中,每個位置可以有10個數字,從0到9,但在二進制中,每個位置只能是0或1。位權的概念是類似的,從右到左,第一位為1,然后依次乘以2,即第二位為2,第三位為4,依次類推。

看一些數字的例子吧:

二進制 十進制
10 2
11 3
111 7
1010 10

負整數的二進制表示

十進制的負數表示就是在前面加一個負數符號-,例如-123。但二進制如何表示負數呢?

其實概念是類似的,二進制使用最高位表示符號位,用1表示負數,用0表示正數。

但哪個是最高位呢?整數有四種類型,byte/short/int/long,分別占1/2/4/8個字節,即分別占8/16/32/64位,每種類型的符號位都是其最左邊的一位。

為方便舉例,下面假定類型是byte,即從右到左的第8位表示符號位。

但負數表示不是簡單的將最高位變為1,比如說:

  • byte a = -1,如果只是將最高位變為1,二進制應該是10000001,但實際上,它應該是11111111。
  • byte a=-127,如果只是將最高位變為1,二進制應該是11111111,但實際上,它卻應該是10000001。 

和我們的直覺正好相反,這是什么表示法?這種表示法稱為補碼表示法,而符合我們直覺的表示稱為原碼表示法,補碼表示就是在原碼表示的基礎上取反然后加1。取反就是將0變為1,1變為0。

負數的二進制表示就是對應的正數的補碼表示,比如說:

  • -1:1的原碼表示是00000001,取反是11111110,然后再加1,就是11111111。
  • -2:2的原碼表示是00000010,取反是11111101,然后再加1,就是11111110。
  • -127:127的原碼表示是01111111,取反是10000000,然后再加1,就是10000001。 

給定一個負數二進制表示,要想知道它的十進制值,可以采用相同的補碼運算。比如:10010010,首先取反,變為01101101,然后加1,結果為01101110,它的十進制值為110,所以原值就是-110。直覺上,應該是先減1,然后再取反,但計算機只能做加法,而補碼的一個良好特性就是,對負數的補碼表示做補碼運算就可以得到其對應整數的原碼,正如十進制運算中負負得正一樣。

byte類型,正數最大表示是01111111,即127,負數最小表示(絕對值最大)是10000000,即-128,表示范圍就是 -128到127。其他類型的整數也類似,負數能多表示一個數。

負整數為什么采用補碼呢?

負整數為什么要采用這種奇怪的表示形式呢?原因是:只有這種形式,計算機才能實現正確的加減法。

計算機其實只能做加法,1-1其實是1+(-1)。如果用原碼表示,計算結果是不對的。比如說:

1  -> 00000001
-1 -> 10000001
+ ------------------
-2 -> 10000010

用符合直覺的原碼表示,1-1的結果是-2。

如果是補碼表示:

1  -> 00000001
-1 -> 11111111
+ ------------------
0  ->  00000000 

結果是正確的。

再比如,5-3:

5  -> 00000101
-3 -> 11111101
+ ------------------
2  ->  00000010 

結果也是正確的。

就是這樣的,看上去可能比較奇怪和難以理解,但這種表示其實是非常嚴謹和正確的,是不是很奇妙?

理解了二進制加減法,我們就能理解為什么正數的運算結果可能出現負數了。當計算結果超出表示范圍的時候,最高位往往是1,然后就會被看做負數。比如說,127+1:

127   -> 01111111
1     -> 00000001
+ ------------------
-128  -> 10000000 

計算結果超出了byte的表示范圍,會被看做-128。

十六進制

二進制寫起來太長,為了簡化寫法,可以將四個二進制位簡化為一個0到15的數,10到15用字符A到F表示,這種表示方法稱為16進制,如下所示:

2進制 10進制 16進制
1010 10 A
1011 11 B
1100 12 C
1101 13 D
1110 14 E
1111 15 F

可以用16進制直接寫常量數字,在數字前面加0x即可。比如10進制的123,用16進制表示是0x7B,即123 = 7*16+11。給整數賦值或者進行運算的時候,都可以直接使用16進制,比如:

int a = 0x7B;

Java中不支持直接寫二進制常量,比如,想寫二進制形式的11001,Java中不能直接寫,可以在前面補0,補足8位,為00011001,然后用16進制表示,即 0x19。

查看整數的二進制和十六進制表示

在Java中,可以方便的使用Integer和Long的方法查看整數的二進制和十六進制表示,例如:

int a = 25;
System.out.println(Integer.toBinaryString(a)); //二進制
System.out.println(Integer.toHexString(a));  //十六進制
System.out.println(Long.toBinaryString(a)); //二進制
System.out.println(Long.toHexString(a));  //十六進制

位運算

位運算是將數據看做二進制,進行位級別的操作,Java不能單獨表示一個位,但是可以用byte表示8位,可以用16進制寫二進制常量。比如: 0010表示成16進制是 0x2, 110110表示成16進制是 0x36。

位運算有移位運算和邏輯運算。

移位有:

  • 左移:操作符為<<,向左移動,右邊的低位補0,高位的就舍棄掉了,將二進制看做整數,左移1位就相當於乘以2。
  • 無符號右移:操作符為>>>,向右移動,右邊的舍棄掉,左邊補0。
  • 有符號右移:操作符為>>,向右移動,右邊的舍棄掉,左邊補什么取決於原來最高位是什么,原來是1就補1,原來是0就補0,將二進制看做整數,右移1位相當於除以2。

例如:

int a = 4; // 100
a = a >> 2; // 001,等於1
a = a << 3 // 1000,變為8

邏輯運算有:

  • 按位與 &:兩位都為1才為1
  • 按位或 |:只要有一位為1,就為1
  • 按位取反 ~: 1變為0,0變為1
  • 按位異或 ^ :相異為真,相同為假

大部分都比較簡單,就不詳細說了。具體形式,例如:

int a = ...; 
a = a & 0x1 // 返回0或1,就是a最右邊一位的值。
a = a | 0x1 //不管a原來最右邊一位是什么,都將設為1

小結

本節我們討論了整數的二進制表示,需要注意的就是負數的二進制表示,以及計算機進行二進制加減操作的過程,從而我們就能理解為什么有的時候正整數計算會出現負數。

我們同樣討論了整數的位運算,需要注意的就是無符號右移和有符號右移的區別。

理解了整數,那小數呢?

---------------- 

未完待續,查看最新文章,敬請關注微信公眾號“老馬說編程”(掃描下方二維碼),深入淺出,老馬和你一起探索Java編程及計算機技術的本質。原創文章,保留所有版權。

-----------

更多相關原創文章

計算機程序的思維邏輯 (1) - 數據和變量

計算機程序的思維邏輯 (2) - 賦值

計算機程序的思維邏輯 (3) - 基本運算

計算機程序的思維邏輯 (5) - 小數計算為什么會出錯?

計算機程序的思維邏輯 (6) - 如何從亂碼中恢復 (上)?

計算機程序的思維邏輯 (7) - 如何從亂碼中恢復 (下)?

計算機程序的思維邏輯 (8) - char的真正含義


免責聲明!

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



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