JAVA:byte和int類型的轉換-原碼反碼補碼

byte是什麼?

Java中,byte在內存中佔一個字節。計算機是用二進制來表示數據的,一個字節也就是8個比特位,可以表示2^8=256個數,範圍爲(00000000011111111),即0256。
順便說一下,byte 8位二進制 = 1個字節
char 2個字節 short (2個字節) int(4個字節) long(8個字節) float (4個字節) double(8個字節)

有符號數是什麼?

在實際的程序語言中(比如Java),又劃分爲有符號,與無符號。
對於有符號數,最高位表示符號位,最高位爲1時爲負數,最高位爲0是正數。
由於最高位是符號位,所以可表示數的範圍只有7位。因此其絕對值最大範圍爲0-127,即2^7=128。

一個字節取值範圍爲何是-128~127?

根據上文所述,最高位爲符號位,剩下七位表示數據,可以表示0-2^7。
如果一個數是正數,最大數則爲:01111111,轉爲十進制爲127,
如果一個數是負數,按照一般人都會覺得是11111111,轉爲十進制爲-127,
但是:一個+0表示爲:00000000,一個-0表示爲:1000000,因爲符號位不算在裏面,所以就會有兩個0,所以從一開始發明二進制的時候,就把-0規定爲-128,如此二進制的補碼就剛好在計算機中運作中吻合。(這是國內教材中的解釋)
公式:計算一個數據類型的數據大小範圍:-2(字節數*7)~2(字節數*7)-1
故byte的取值範圍爲1000 0000 到 0111 1111

補碼?

在Java中,是採用補碼來表示二進制數。

知道原碼如何求補碼?

正數的補碼和原碼相同,負數的補碼是在原碼的基礎上各位取反然後加1。

知道補碼如何求原碼?

已知一個數的補碼,求原碼的操作其實就是對該補碼再求補碼。
補碼轉換爲原碼:符號位不變,數值位按位取反,末位權再加1。即補碼的補碼等於原碼。
正整數的原碼、反碼和補碼是一樣的,即看到符號位(第一位)是0,就可以照着寫出其他兩種碼。所以已知正數的補碼,求其原碼,兩個數是一樣的。

舉例

已知補碼爲1000000,求其原碼
11111111+1=10000000(超出範圍捨去)
在這裏插入圖片描述

數據類型轉換:int類型和byte類型的轉換

int類型在內存中佔用了4個字節,也就是32位。int類型是有符號的,因此,32位並不會全部用來存儲數據,使用最高位來存儲符號,最高位是0,表示數據是正數,最高位是1,表示數據是負數,使用其他的31位來存儲數據。
注意:在Java中,是採用補碼來表示二進制數

int temp = 30;
System.out.println("二進制:" + Integer.toBinaryString(temp));
int temp = -30;
System.out.println("二進制:" + Integer.toBinaryString(temp));
二進制:11111111111111111111111111100010
二進制:11110

舉例-正數(正數的補碼和原碼相同):
int類型的1,在內存中表示就是最高位爲0,數值爲1,其餘位補0
0000 0000 0000 0000 0000 0000 0000 0001
int類型的128,在內存中表示就是最高位爲0
0000 0000 0000 0000 0000 0000 1000 0000

舉例-負數(負數的補碼是在原碼的基礎上各位取反然後加1):
int類型的-30,在內存中表示就是最高位爲1,數值爲11110
原碼爲
1000 0000 0000 0000 0000 0000 0001 1110
由於計算機存儲的是二進制的補碼,補碼爲原碼除最高位外,取反+1

1111 1111 1111 1111 1111 1111 1110 0001
+                                     1
=
1111 1111 1111 1111 1111 1111 1110 0010

數據類型的轉換,分爲自動轉換和強制轉換。自動轉換是程序在執行過程中“悄然”進行的轉換,不需要用戶提前聲明,一般是從位數低的類型向位數高的類型轉換;強制類型轉換則必須在代碼中聲明,轉換順序不受限制。

自動轉換:byte轉int

自動轉換按從低到高的順序轉換。不同類型數據間的優先關係如下:
低--------------------------------------------->高
byte,short,char-> int -> long -> float -> double

byte valueByte=127;
int valueInt=valueByte;
System.out.println(valueInt);
127

自動轉換,不會出現數據錯誤。前提是賦給valueByte的值在-128~127範圍內,如果超出範圍,ide會自動提醒:
在這裏插入圖片描述
如上,把賦值當做int類型處理,需要int類型強制轉換爲byte後再賦給valueByte.

byte valueByte= (byte) 128;
int valueInt=valueByte;
System.out.println(valueInt);
-128

這個時候產生的數據錯誤,是因爲第一行代碼就產生了強制轉換。

int轉byte

強制轉化,需要在代碼中聲明
高--------------------------------------------->低
double->float -> long -> int -> byte,short,char

如果|value|<=127

強制轉換爲byte類型後,value數值範圍未超過byte可顯示範圍,截取到的數據是正確顯示的;

int value = 30;
System.out.println((byte)value);
30
int value = -30;
System.out.println((byte)value);
-30

如果|value|>127

此時數據超出範圍,截取到的數據就會發生錯誤

int value = 128;
System.out.println((byte)value);
-128
int value = -128;
System.out.println((byte)value);
-128

爲什麼會這樣呢?

128發生錯誤

因爲128已超出byte的可表示範圍,前面說過int類型的128在內存中存儲爲
0000 0000 0000 0000 0000 0000 1000 0000
這時候對128進行強制類型轉換,因爲byte只有1字節,即8位,所以這個二進制串就要被截短,截取的規則是:只保留低8位:10000000。
被截取掉的只是1前面的24個0,看起來好像並沒有影響數據大小,但這個時候的二進制串還表示128嗎?
不是了,因爲byte的最高位代表的是符號位。也就是說現在1被用來作符號位了,即代表是負數, 後面的7個0000000才用來表示數值,結合起來1 0000000 代表的就是-128。

-128未發生錯誤

因爲-128沒有超出取值範圍
11111111111111111111111110000000
轉換爲byte,只剩下了1000 0000,正好是-128。
如果測試一下-129,就會發現結果也是錯誤的,因爲他也超出範圍了。到此我們已經知其所以然了,詳情可以自己去試。

結論:int轉爲byte後,是否可以正確的轉回來?

如果int值範圍爲-128~127,int值和byte值一致。
如果int值大於255或者小於-256,值肯定是還原不了,信息已經丟失一部分。
如果int值大於127小於等於255,第八位是1,相應的byte是負值,使用int x = b&0xff,因爲b在表達式裏面會自動提升爲int,所以會在高位補齊1(按符號位補齊),因爲我們知道對應的int是正值,所以這時把高於8位的1全部換成0。
例如:128

原數據:0000 0000 0000 0000 0000 0000 1000 0000
轉化爲byte1000 0000
還原數據:
       1111 1111 1111 1111 1111 1111 1000 0000
      &
0xff:  0000 0000 0000 0000 0000 0000 1111 1111
=
       0000 0000 0000 0000 0000 0000 1000 0000

如果int值小於-128大於等於-256,第八位是0,前面所有位都是1,相應的byte是正值,使用int x=b|0xffffff00,b在表達式中自動提升爲int,所以高位補齊0(按符號位補齊),把第八位前面所有的0替換成1,就可以得到正確的值。
例如:-129

原數據:1111 1111 1111 1111 1111 1111 0111 1111
轉化爲byte0111 1111
還原數據:
             0000 0000 0000 0000 0000 0000 0111 1111
      |
0xffffff00:  1111 1111 1111 1111 1111 1111 0000 0000
=
             1111 1111 1111 1111 1111 1111 0111 1111

由於由於int有32位,byte只有8位,只能強制轉換截取低八位。而在byte表示中,雖然有八位,但只有七位是用來表示數據的,最高位爲符號位,只表示正負,可以表示數的範圍爲(-128~127)

一個使用實例

背景:

和下位機進行通信時,需要交換溫度信息。協議規定,溫度由2 bytes表示,有符號整數,高字節在前,低字節在後。除以100轉換爲精確到小數點後兩位的浮點數值,表示設置的攝氏溫度值。

分析:

下位機發送數據:50度,50*100=5000,轉化爲二進制1 0011 1000 1000
高字節,爲高八位,未佔滿補0:0001 0011
低字節,爲低八位:1000 1000
下位機傳過來就是一個這樣的數據,那麼我們如何拼接回去呢?

int temp = 5000;
byte low = (byte) (temp & 0xff);
byte high = (byte) ((temp & 0xFF00) >> 8);
int result = ((high & 0xff) << 8) + (low & 0xff);
System.out.println(result);
5000

那麼這段代碼究竟做了什麼?

前三行,就是我們上面說的,下位機做的事情。第四行就是上位機需要做的事情。

1、上位機分解:取低八位

byte low = (byte) 5000;

整型數在計算機中用補碼存儲的。5000轉化爲二進制爲1 0011 1000 1000,表示爲int類型:
0000 0000 0000 0000 0001 0011 1000 1000
強轉爲byte,取低八位 =-120,轉化爲十六進制爲88

1000 1000

轉化爲十六進制=88

2、上位機分解:取高八位

 byte high = (byte) ((temp & 0xFF00) >> 8);

取高八位:

0000 0000 0000 0000 0001 0011 1000 1000 
&
0000 0000 0000 0000 1111  1111  0000 0000
=
0000 0000 0000 0000  0001 0011 0000 0000
右移八位
0000 0000 0000 0000 0000 0000  0001 0011

強轉爲byte,保留低八位

0001 0011

=19,轉化爲十六進制=13
由上可看出&0xFF00只是爲了保留高八位的值

3、下位機合成:恢復高八位

(high & 0xff) << 8
                               0001 0011 
&
0000 0000 0000 0000 0000 0000 1111  1111  
=
0000 0000 0000 0000 0000 0000  0001 0011
左移八位
0000 0000 0000 0000  0001 0011 0000 0000

4、下位機合成:恢復低八位

low & 0xff
                               1000 1000
0000 0000 0000 0000 0000 0000 1111  1111  
=
0000 0000 0000 0000 0000 0000 1000 1000

5、下位機合成:合成

int result = ((high & 0xff) << 8) + (low & 0xff);
0000 0000 0000 0000  0001 0011 0000 0000
+
0000 0000 0000 0000 0000 0000 1000 1000
=
0000 0000 0000 0000 0001 0011 1000 1000=5000

就這樣,從下位機分解發送到上位機接收恢復,一個例子就完成了。
但是協議裏還規定了temp 爲有符號數。
所以我們試試溫度爲負

溫度爲負數時

int temp = -30;
byte low = (byte) (temp & 0xff);
byte high = (byte) ((temp & 0xFF00) >> 8);
int result = ((high & 0xff) << 8) + (low & 0xff);
System.out.println(result);
65506

結果顯然發生了錯誤,這是爲什麼呢?
重新走一遍流程

1、上位機分解:取低八位

byte low = (byte) -30;

負數存儲補碼爲:

1111 1111 1111 1111 1111 1111 1110 0010

強轉爲byte,取低八位

1110 0010

2、上位機分解:取高八位

 byte high = (byte) ((temp & 0xFF00) >> 8);
1111 1111 1111 1111 1111 1111 1110 0010
&
0000 0000 0000 0000 1111  1111  0000 0000
=
0000 0000 0000 0000 1111  1111 0000 0000
右移八位
0000 0000 0000 0000 0000 0000  1111  1111

強轉爲byte,保留低八位

1111  1111

3、下位機合成:恢復高八位

(high & 0xff) << 8
                               1111  1111
&
0000 0000 0000 0000 0000 0000 1111  1111  
=
0000 0000 0000 0000 0000 0000  1111  1111
左移八位
0000 0000 0000 0000  1111  1111 0000 0000

4、下位機合成:恢復低八位

low & 0xff
                              1110 0010
0000 0000 0000 0000 0000 0000 1111  1111  
=
0000 0000 0000 0000 0000 0000 1110 0010

5、下位機合成:合成

int result = ((high & 0xff) << 8) + (low & 0xff);
0000 0000 0000 0000  1111  1111 0000 0000
+
0000 0000 0000 0000 0000 0000 1110 0010
=
0000 0000 0000 0000 1111  1111 1110 0010=65506

結論:

觀察原始數據我們發現

1111 1111 1111 1111 1111  1111 1110 0010
0000 0000 0000 0000 1111  1111 1110 0010

我們的結果本來應該是沒錯的,但是由於原數據拆成了了兩個字節,也就是16位,但是int類型卻有32位,重新合成數據時,後16位的數據是正確的,但是前16位,因爲合成的時候不知道正負,按正數與就出問題了。
如果我們一開始就知道傳過來的是負數,只要把結果|0xffff0000就可以了

int temp = -30;
byte low = (byte) (temp & 0xff);
byte high = (byte) ((temp & 0xFF00) >> 8);
int result = ((high & 0xff) << 8) + (low & 0xff);
System.out.println(result|0xffff0000);
-30
  0000 0000 0000 0000 1111  1111 1110 0010
  |
  1111 1111 1111 1111 1111 1111 0000 0000
=
  1111 1111 1111 1111 1111 1111 1110 0010

是不是很麻煩,需要判斷正負返回不同的結果。
其實還有一個簡單的辦法,我們只需要,將接收數據的result類型轉化爲short就好了,因爲short類型也是16位,便不會出現需要按正負號補齊符號位的問題了。

int temp = -30;
byte low = (byte) (temp & 0xff);
byte high = (byte) ((temp & 0xFF00) >> 8);
short result = (short)(((high & 0xff) << 8) + (low & 0xff));
System.out.println(result);
-30

最後,關於我們爲什麼要&0xff之類的操作?

在java中,0xff是一個int類型的數據, 實際上是0x000000ff。
int c = a[0]&0xff;
實際上是 int c = (int)a[0]&0x000000ff;
c 實際上取的是 (int)a[0]的低8比特數據而已。

byte類型轉換爲int類型,
當你希望保證低8比特數據一致,前24比特爲0時要與上0xff。
當你希望保證低16比特數據一致,前16比特爲0時要與上0xffff。

Java基礎不好的小水怪,正在學習。有錯請指出,一起加油。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章