一 參考博文
java中無符號類型的解決方案
原碼、反碼、補碼知識詳細講解(此作者是我找到的講的最細最明白的一個)
0x80000000為什么等於-2147483648和負數如何在內存上儲存
二 java中的無符號數和有符號數
在計算機中,可以區分正負的類型,稱為有符號類型,無正負的類型,稱為無符號類型。
- 使用二進制中的最高位表示正負
計算機中用補碼表示數值;另外,用二進制的最高位表示符號,0表示正數、1表示負數。 - 無符號和有符號數的范圍的區別
無符號數中,所有的位都用於直接表示該值的大小;有符號數中最高位用於表示正負,所以,正值時,該數的最大值就會變小:
無符號數:1111 1111 值:255
有符號數:0111 1111 值:127
同樣一個字節,無符號的最大值是255,有符號的最大值是127
三 java中的基本類型
Java的原始類型里除了char是無符號類型之外,其他都是有符號數據類型,如果需要某個寬度的無符號類型,可以用>>>進行轉化,這個是java的無符號右移操作符,或者使用下一個寬度的帶符號類型來模擬,
例如,需無符號的short,就用int來模擬:
int toUnsigned(short s) {
return s & 0x0FFFF;
}
java中十進制的字面常理只有一個特性,就是所有的十進制字面常量都是正數,如果想寫一個負的十進制,則需要在正的十進制字面常量前面加上“-”就好了。
但是十六進制或者八進制的字面常量就不一定是正數或者負數,如果最高位是1,那么就是負數:
System.out.println(0x80);//128
//0x81看作是int型,最高位(第32位)為0,所以是正數
System.out.println(0x81);//129
System.out.println(0x8001);//32769
System.out.println(0x70000001);//1879048193
//字面量0x80000001為int型,最高位(第32位)為1,所以是負數
System.out.println(0x80000001);//-2147483647
//字面量0x80000001L強制轉為long型,最高位(第64位)為0,所以是正數
System.out.println(0x80000001L);//2147483649
四 補碼與真值
這里先看一個問題:
@Test
public void test01(){
System.out.println(0x80000000); // -2147483648
}
這個結果是怎么得來的?
要搞明白這個問題,得先明白幾個概念:
- 機器數:
一個數在計算機中的二進制表示形式, 叫做這個數的機器數。機器數是帶符號的,在計算機用一個數的最高位存放符號, 正數為0, 負數為1.
比如,十進制中的數 3 ,計算機字長為8位,轉換成二進制就是00000011。如果是 -3 ,就是 10000011 。那么,這里的 00000011 和 10000011 就是機器數 - 真值:
因為第一位是符號位,所以機器數的形式值就不等於真正的數值。例如上面的有符號數 10000011,其最高位1代表負,其真正數值是 -3 而不是形式值131(10000011轉換成十進制等於131)。
所以,為區別起見,將帶符號位的機器數對應的真正數值稱為機器數的真值(即補碼表示的值)。
例:0000 0001的真值 = +000 0001 = +1,1000 0001的真值 = –000 0001 = –1 - 計算真值
就拿-3來說,機器數為 10000011,那么補碼是 11111101,所以真值就是補碼的值:
補碼求值公式:補碼的最高位有效位乘以(-1),然后按一般求二進制的方法求值
例如:
-3的補碼 11111101 = (-1)12^7 + 12^6+.... 12^0 = -3
3的補碼 00000011 = (-1)027+........1*20= 3 - 0x80000000問題解析
再來看0x80000000為什么等於-2147483648,Java中用此十六進制表示int的最小值:
/**
* A constant holding the minimum value an {@code int} can
* have, -2<sup>31</sup>.
*/
@Native public static final int MIN_VALUE = 0x80000000;
此十六進制數內存中存儲的的確是0x80000001的二進制碼。因為使用十六進制給int賦值時,這里的十六進制是補碼形式。
也就是說,我們給變量賦的是補碼,不是源碼,所以會直接把0x80000001這個補碼存入內存
補碼求值得: 0x80000000 = (-1)1231+.....+0*20 = -2147483648
所以這個值是這樣來的!
五 java中的數據類型符號擴展
先看一個jdk源碼中int轉為long用到的方法:
@Test
public void test03(){
final long l = -5 & 0xffffffffL;
System.out.println(l); // 4294967291
}
如果運算一個操作數是long型,而另一個操作數是int類型。為了執行該計算,Java將int類型的數值用拓寬原生類型轉換提升為long類型,然后對兩個long類型數值相加。
因為int是有符號的整數類型,所以這個轉換執行的是符號擴展。
-5 轉換為long再轉換為二進制,0xffffffff轉換為二進制
進行與運算:
1111111111111111111111111111111111111111111111111111111110000101
0000000000000000000000000000000011111111111111111111111111111111
---------------------------------------------------------------------- & 與運算,兩個都為1才為1,否則為0
0000000000000000000000000000000011111111111111111111111110000101= 4294967173 (十進制)
為什么-5轉long前面要補1呢,這里就需要知道符號擴展規則:
窄的整型轉換成較寬(字節數多)的整型時符號擴展規則:
如果最初的數值類型是有符號的,那么就執行符號擴展(即如果符號位為1,則擴展為1,如果為零,則擴展為0);
如果它是char,那么不管它將要被提升成什么類型,都執行零擴展,
如果將一個char數值c轉型為一個寬度更寬的整型,並且希望有符號擴展,那么就先將char轉型為一個short,它與char上個具有同樣的寬度,但是它是有符號的
寬的整型轉換成窄的整型直接截取低位的值,高位扔掉
所以上面-5符號是1,所以進行符號擴展前面都補1,補成long(64位),再進行位運算得出結果!
六 Java中byte轉換int時與0xff進行與運算的原因
jdk源碼中byte轉int用到了 & 0xff,比如String的API:
public static char charAt(byte[] value, int index) {
if (index < 0 || index >= value.length) {
throw new StringIndexOutOfBoundsException(index);
}
return (char)(value[index] & 0xff);// 先轉int,再轉char
}
這里為什么要用與運算呢? 因為char是無符號類型,所以不能進行符號擴展,需要零擴展,即前面補0
窄整型->寬整型要進行符號擴展,這里byte->cahr是窄到寬,如果不想進行符號擴展,則需要&0xff處理,先轉int消除掉符號擴展,再轉char即可
(b & 0xff)的結果是32位的int類型,前24被強制置0,后8位保持不變,然后轉換成char型時,直接截取后16位。這樣不管b是正數還是負數,轉換成char時,都相當於是在左邊補上8個0,即進行零擴展而不是符號擴展
至於為什么要進行零擴展: 因為char是無符號類型,他會把 1111 1111 當做65535而不是-1,,所以你前面補1的話數就會變很大,所以這里需要進行0擴展,於是 & 0xff這種騷操作就來了,這里確實有點繞!如果不看源碼(並且要認真看啊,哈哈)一般發現不了這種問題
再比如下面代碼:
@Test
public void test01(){
byte b=-1;
System.out.println((int)b); // -1
System.out.println(b & 0xff); // 255
}
這里第二行255應該都好說,高位清零就是,至於直接強轉為-1,那么符號擴展之后補碼為11111111111111111111111111111111,求出結果原碼:100000000000000000000000000001 還是-1,所以就是上面的結果,原理就是這樣!
主要就是一個符號擴展延伸出來的問題!