原文鏈接 Java中整數(shù)基礎(chǔ)知識
最近做了一道題,非常有意思,題本身很簡單枷莉,但涉及到整數(shù)的最大值以及最小值击纬,當(dāng)寫測試用例的時(shí)候虐呻,卻犯了一個(gè)錯(cuò)誤绊困,發(fā)現(xiàn)最小整數(shù)并不是0xFFFFFFFF去团,我們來仔細(xì)看一下抡诞。
[圖片上傳失敗...(image-5c2114-1698067059650)]
整數(shù)基礎(chǔ)
Java中,整數(shù)都是有符號的土陪,最高位是符號位昼汗,0表示正數(shù),1表示負(fù)數(shù)鬼雀。有四種顷窒,byte,short取刃,int和long蹋肮。
- byte 8位,-2^7 ~ 2^7 - 1璧疗,-128 ~ 127, 0x80 ~ 0x7F
- short 16位,-2^15 ~ 2^15 - 1馁龟,-32768 ~ 32767, 0x8000 ~ 0x7FFF
- int 32位崩侠,-2^31 ~ 2^31 -1,-2147483648 ~ 2147483647, 0x8000000 ~ 0x7FFFFFFF
System.out.println(String.format("Max byte %d, 0x%X, half 0x%X", Byte.MAX_VALUE, Byte.MAX_VALUE, Byte.MAX_VALUE/2));
System.out.println(String.format("Min byte %d, 0x%X, half 0x%X", Byte.MIN_VALUE, Byte.MIN_VALUE, (byte)(Byte.MIN_VALUE/2)));
System.out.println(String.format("Max short %d, 0x%X, half 0x%X", Short.MAX_VALUE, Short.MAX_VALUE, Short.MAX_VALUE/2));
System.out.println(String.format("Min short %d, 0x%X, half 0x%X", Short.MIN_VALUE, Short.MIN_VALUE, (short) (Short.MIN_VALUE/2)));
System.out.println(String.format("Max Int %d, 0x%X, half 0x%X", Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE/2));
System.out.println(String.format("Min Int %d, 0x%X, half 0x%X", Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE/2));
// Outputs
//Max byte 127, 0x7F, half 0x3F
//Min byte -128, 0x80, half 0xC0
//Max short 32767, 0x7FFF, half 0x3FFF
//Min short -32768, 0x8000, half 0xC000
//Max Int 2147483647, 0x7FFFFFFF, half 0x3FFFFFFF
//Min Int -2147483648, 0x80000000, half 0xC0000000
細(xì)節(jié)和原理
值得注意的是坷檩,16進(jìn)制的數(shù)值與直覺預(yù)期并不一樣却音,特別是負(fù)數(shù)改抡。正數(shù)是一致的,比如int系瓢,一共是32位阿纤,最高位是符號,所以真正數(shù)值部分是31位夷陋,那么最大的int就是0x7FFFFFF欠拾。
但負(fù)數(shù),也即是最小的int骗绕,卻與直覺完全不一樣藐窄。按照直覺,負(fù)數(shù)最高位是1酬土,那最小的int應(yīng)該是0xFFFFFFFF啊荆忍,為何確是0x80000000呢?原因就是整數(shù)的編碼方式并不是直接的二進(jìn)制形式的撤缴,是以補(bǔ)碼的形式刹枉,也就是說在內(nèi)部實(shí)現(xiàn)中,用二進(jìn)制表示一個(gè)整數(shù)的時(shí)候屈呕,是以二進(jìn)制補(bǔ)碼形式(轉(zhuǎn)成二進(jìn)制后還要求其補(bǔ)碼嘶卧,才是真實(shí)的二進(jìn)制和16進(jìn)制形式)。
簡單來說凉袱,補(bǔ)碼是一種二進(jìn)制編碼形式芥吟,正數(shù)的補(bǔ)碼就是它的本身,而負(fù)數(shù)的補(bǔ)碼是其取反后加1专甩,可以參考百科上的定義钟鸵。
負(fù)數(shù)的轉(zhuǎn)換:原碼取反加1,即是補(bǔ)碼涤躲,補(bǔ)碼再轉(zhuǎn)補(bǔ)碼即得到原碼(也可以補(bǔ)碼減1再取反即是原碼)棺耍,符號位在轉(zhuǎn)換過程中一直不變。
注意:補(bǔ)碼的嚴(yán)謹(jǐn)說法是2的補(bǔ)碼(Twi's Complement)种樱,并不稱作二進(jìn)制補(bǔ)碼蒙袍。補(bǔ)碼是為了能用加法的方式來計(jì)算減法。因此原碼轉(zhuǎn)補(bǔ)碼時(shí)嫩挤,取反加1害幅,補(bǔ)碼求原碼時(shí)再求補(bǔ)碼,避免用減法(雖然減1后再取反也能求得原碼岂昭,但需要用到減法)以现。
非10進(jìn)制字面常量是補(bǔ)碼形式
在日常代碼中,為了方便,通常都用16進(jìn)制來寫一些整數(shù)常量邑遏,這里就特別要注意了佣赖。16進(jìn)制的字面常量,不會再進(jìn)行補(bǔ)碼轉(zhuǎn)換记盒,會當(dāng)成補(bǔ)碼直接使用憎蛤。
System.out.println(String.format("Literal %d, 0x%X", 0xffffffff, 0xffffffff));
//Literal -1, 0xFFFFFFFF
所以,你寫的0xFFFFFFFF是補(bǔ)碼形式纪吮,它的原碼是減1再取反俩檬,(32個(gè)1)減1,最低位變成0彬碱,前面31個(gè)1豆胸,再取反,就只剩下最后一位是1和最高位的符號位巷疼,因此是-1晚胡,注意符號位是不變的,在轉(zhuǎn)換過程中嚼沿。
而最小的整數(shù)是-2^31估盘,原碼 形式應(yīng)該是0x80000000,先取反變成了0xFFFFFFFF骡尽,再加1遣妥,符號位最高位不變的情況下,其余全變成了0攀细,所以是0x80000000箫踩。
最小整數(shù)
開篇時(shí)說了,當(dāng)時(shí)錯(cuò)誤的認(rèn)為0xFFFFFFFF是最小的整數(shù)谭贪,這里犯的第一個(gè)嚴(yán)重錯(cuò)誤是境钟,誤把二進(jìn)制的補(bǔ)碼當(dāng)成了原碼,代碼中的16進(jìn)制(二進(jìn)制)都是補(bǔ)碼形式的俭识,它的原碼是0x80000001即-1慨削。這個(gè)錯(cuò)誤是比較明顯的。
但另外的問題就是套媚,假如都是二進(jìn)制原碼的情況下缚态,為啥最小的整數(shù)是0x80000000而不是0xFFFFFFFFF。這是理解上的誤區(qū)堤瘤,整數(shù)的定義是玫芦,最高位是符號位,所以常規(guī)認(rèn)知是全是1的情況是最大的數(shù)宙橱,加上符號不就變成最小的了么姨俩?這是以10進(jìn)制思維蘸拔,也就是二進(jìn)制轉(zhuǎn)換成為10進(jìn)制后的想法师郑。計(jì)算機(jī)只認(rèn)識二進(jìn)制环葵,在最高位是1(負(fù)數(shù))的情況下,哪個(gè)數(shù)最斜γ帷张遭?當(dāng)然0x80000000最小啊,它除了符號位全是0地梨,肯定 小于0xFFFFFFFF菊卷,因此從二進(jìn)制的角度來理解,0x80000000是最小的整數(shù)宝剖。
而0xFFFFFFFF(原碼)則是第2小的負(fù)整數(shù)洁闰,最高位是符號位,其余31位全是1万细,它的補(bǔ)碼是0x80000001:
System.out.println(String.format("Literal %d 0x%X", 0x80000001, 0x80000001));
//Literal -2147483647 0x80000001
計(jì)算機(jī)中是以二進(jìn)制補(bǔ)碼來存儲整數(shù)的扑眉,所以要從計(jì)算機(jī)的角度來理解比較,就是要用二進(jìn)制的補(bǔ)碼來比較兩個(gè)數(shù)的大小赖钞。
再次強(qiáng)調(diào)腰素,我們寫的源碼當(dāng)中的二進(jìn)制(無論是字面常量,還是打印輸出)都是補(bǔ)碼形式雪营,計(jì)算機(jī)看到的也是補(bǔ)碼弓千,比較也是補(bǔ)碼,只有當(dāng)轉(zhuǎn)換成為10進(jìn)制時(shí)献起,才會還原為原碼并進(jìn)行10進(jìn)制轉(zhuǎn)換洋访。
由此得出,(注意谴餐,程序員眼睛看到的16進(jìn)制全是補(bǔ)碼形式):
- 0x80000000是最小的負(fù)數(shù)姻政,原碼為0x80000000,-2^31
- 0x80000001总寒,第2小的負(fù)數(shù)(最小的0x80000000再加上1)扶歪,原碼為0xFFFFFFFF,-(2^31-1)
- 0xFFFFFFFF摄闸,是-1善镰,原碼為0x80000001。它是最大的負(fù)數(shù)(-1是最大的負(fù)數(shù))年枕。0xFFFFFFFF(全是1)肯定 最大啊炫欺,最高位是1,是負(fù)數(shù)熏兄,所以是最大的負(fù)數(shù)品洛。
一些有意思的值
Integer.MAX_VALUE + 1 = Integer.MIN_VALUE
按理說應(yīng)該溢出了树姨,但如果以16進(jìn)制去計(jì)算,就是這樣的結(jié)果:0x7FFFFFFF + 1 = 0x80000000
System.out.println(String.format("Max in %d (0x%X) + 1 = %d (0x%X)", Integer.MAX_VALUE, Integer.MAX_VALUE, (Integer.MAX_VALUE+1), (Integer.MAX_VALUE+1)));
// Max in 2147483647 (0x7FFFFFFF) + 1 = -2147483648 (0x80000000)
Integer.MIN_VALUE - 1 = Integer.MAX_VALUE
System.out.println(String.format("Min in %d (0x%X) - 1 = %d (0x%X)", Integer.MIN_VALUE, Integer.MIN_VALUE, (Integer.MIN_VALUE-1), (Integer.MIN_VALUE-1)));
//Min in -2147483648 (0x80000000) - 1 = 2147483647 (0x7FFFFFFF)
參考資料
- 為什么0xffffffff是-1桥状?(計(jì)算機(jī)對整型的存儲)
- 原碼, 反碼, 補(bǔ)碼 詳解
- 一文讀懂原碼帽揪、反碼與補(bǔ)碼
- 二進(jìn)制的原碼、反碼辅斟、補(bǔ)碼
- 關(guān)于2的補(bǔ)碼