日常刷題中,然後有個地方是要求將當前數變成絕對值纔可以使用;然後開開心心寫了如下代碼:
long a = Math.abs(Integer.MIN_VALUE);
System.out.println(a);
結果結果卻讓我大跌眼鏡。但是一番分析下來倒是很有意思
點開源碼(JDK1.8環境下)
這段話翻譯過來就是,
如果abs方法輸入參數爲正數,就返回那個值;如果未負數,就返回這個負數的對應絕對值;但是如果輸入值爲Integer.MIN_VALUE時;就返回輸入值
這樣看起來問題是解決了,但是身爲一個合格的程序員,怎麼可以不弄清楚原因呢。
1.原因分析
要講清上面發生的原因,就必須把幾個核心的問題都講清楚;
1.1 原碼
一開始人們是通過原碼來表示整數,整數有正負之分,便通過符號位+二進制
的方式表示。(最高位(最左邊)取0表示正數,取1表示負數。然後加上對應的二進制。
)
+1 = 0000 0001
-1 = 1000 0001
1.2 反碼
正數的反碼是其本身
負數的反碼是符號位不變 ,其他位都變爲反
[+1] = [00000001]原 = [00000001]反
[-1] = [10000001]原 = [11111110]反
1.3 補碼
正數的補碼就是其本身
負數的補碼是在其原碼的基礎上, 符號位不變, 其餘各位取反, 最後+1. (即在反碼的基礎上+1)
[+1] = [00000001]原 = [00000001]反 = [00000001]補
[-1] = [10000001]原 = [11111110]反 = [11111111]補
那麼爲何要使用補碼呢?
因爲計算機中沒有減法的概念,對應減法 a-b = a+(-b)
;
反碼的方式
1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2
1 - 1 = 1 + (-1) = [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0
補碼的方式
1 - 1 = 1 + (-1) = [0000 0001]補 +[1111 1111]補 = [0000 0000]補=0
所以這裏如果1.8的jdk不限制,就容易導致結果錯誤;可以理解爲
MIN_VALUE[反] = 0x80000000 = 10....0(中間29個0)= 1......1(中間30個1)
MIN_VALUE[補] = 0x80000000 = 10....0(中間29個0) = 1......1(中間30個1)+1 = 0......0(中間30個1)
所以 這裏錯誤了;而且可以發現超出限制後,會形成一個循環
所以可以理解爲爲了防止這樣的循環發生,JDK1.8出現這個保護的模式;而一切的發生源頭就是補碼導致