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
轉化爲byte爲1000 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
轉化爲byte爲0111 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基礎不好的小水怪,正在學習。有錯請指出,一起加油。