《十個爲什麼》之七:爲什麼 Java 整型的負數比正數多一個數字範圍?

        Java 的整型有 byte、short、int、long 四種,其對應的數值範圍如下所示:

整型 可取最小值 可取最大值
byte -128 127
short -32768 32767
int -2147483648 2147483647
long -9223372036854774808 9223372036854774807

        所有的負數範圍都比整數多1個數字,其實這是計算機的存儲和加減運算機制決定的。

        首先,計算的存儲只有0和1,每個位置要麼存0,要麼存1,這些位置又叫做位(即 bit)。

        其次,拿 byte 舉例,它在目前計算的標準中是8位的,也就是說:1 byte = 8 bit;所以一個 byte 在計算機中只有8個可以存放0和1的位置,8個位置放上0或1,窮舉的全部可能性爲2的8次方,即2^8=256。所以,在一個 byte 中最多隻能表示256個不同意義的事物(可以是任何可能的事物),在這裏如果是數字的話,就只能有256個數字了。如果我們不需要用它表示負數,那麼它可以表示0~255這256個數字;如果它需要標識正負,計算機中會用高位表示符號,其他位表示數字,而0表示正號,1表示負號。這時候0開頭的二進制數字表示的範圍是0~127,而1開頭的二進制數字表示的範圍是-0~-127,所以被正負號佔用一位後,實質只能表示-127~127這255個數字而不是256個數字。

        但是,0和-0本質上是沒什麼區別的,如果能夠將-0改爲代表其他數字,那麼表示的範圍就能增加,所以就有了:-0表示-1、-1表示-2、以此類推到-127表示-128這樣的情況。

        雖然上述可以增加範圍,但實際中不會有人爲了增加一個數字範圍而改變這種人類不容易理解的表達方式的,之所以實際情況確實如此表示,是有深層原因的:計算機人員爲了簡化計算機的計算過程和提高效率,不想讓計算機先判斷數字的正負,再使用加法或減法來計算,而是簡化成:計算機只需要進行加法操作,並且忽略正負號的識別。

        要達到這個目的的理論很簡單,就是1-2可以表示爲1+(-2)的,還是用 byte 舉例,

第一步:1-2=-1    這種適合人類思維的計算過程是:00,000,001 - 00,000,010 = 10,000,001(十進制爲-1)

        這裏是識別正負號再人工計算的,這對於計算機來說不夠簡單,效率也不高;

第二步:1-2 改爲:1+(-2)=-1    後,其計算過程爲:00,000,001 + 10,000,010 = 10,000,011(十進制爲-3)

        顯然這個計算過程是不正確的,爲了能夠直接做加法,還需要對負數取反後計算,即將負數除了符號位,其他位全部取反,10,000,010 取反後就是 11,111,101——這種取反後的二進制碼也叫反碼,取反前的二進制碼則叫做原碼。(順便說明下反碼的轉換過程:正數的反碼是其本身,負數的反碼是除了符號位外其他位全部取反;反碼轉正碼只需要逆轉此過程)

第三步:將原碼的計算過程取反後的計算過程爲:

        (原)00,000,001 + (原)10,000,010 = (原)10,000,011(十進制爲-3)

        (反)00,000,001 + (反)11,111,101 = (反)11,111,110(轉爲正碼爲10,000,001,十進制爲-1),計算過程正確。

        可見反碼的出現完全是爲了簡化計算機的底層計算而存在的,這種計算方式忽略了符號僅使用加法就解決了正負數的問題。

        但是,反碼還存在某些問題,如果上面的1-2是2-2的話,那麼結果就是下面的:

        (原)00,000,010 + (原)10,000,010 = (原)10,000,100(十進制爲-4)

        (反)00,000,010 + (反)11,111,101 = (反)11,111,111(轉爲正碼爲1,000,000,十進制爲-0),終於說到這個-0了,這個從人的角度來看當然就是0的意思,但是對於計算機來說,還需要編指令告訴它:-0就是0,不然在計算機眼裏它是兩個不同的數字或信息。這樣一來,就又是增加了計算機的複雜度,所以爲了方便和效率,人們想出讓-0表示-1,-1表示-2……這樣的方式,結果就使得負數會比正數多一個數字的情況了——所以使用這種表達方式的原因並不是爲了多增加一個數字範圍,而是降低計算機的計算複雜度,畢竟前者相比後者,根本不值一提。

        但是使用了這種表示方式後,計算機要怎麼計算呢?這時候就需要用到補碼了。(知識點來了,補碼的轉換過程:正數的補碼是其本身,負數的補碼是除了符號位外其他位全部取反後加1,也就是反碼再加1;補碼轉正碼或反碼只需要逆轉此過程)

第四步:

        這時候我們要講清楚的是-0的問題,所以我們拿2-2的例子來說明,將原碼轉爲補碼後的計算過程爲:

        (原)00,000,010 + (原)10,000,010 = (原)10,000,100(十進制爲-4)

        (反)00,000,010 + (反)11,111,101 = (反)11,111,111(轉爲正碼爲1,000,000,十進制爲-0)

        (補)00,000,010 + (補)11,111,110 = (補)100,000,000(轉爲正碼前需要捨去高位1,因爲我們本來就是隻有8位的,現在變成9位了,是存不下的,結果爲00,000,000,十進制爲0)

        這時候就再也沒有-0出現了,而舍位操作並沒有對計算過程造成任何影響,並且還將-0轉成了0(100,000,000是-0,00,000,000則是0),真的是不得不敬佩想出這整套方法的科學家。

小結:這裏主要講述了計算機的計算過程,而這種計算過程的設計方式引出了反碼和補碼的概念——所以本文還應該有個副標題:爲什麼計算機要有反碼和補碼,明白這些原理,便能輕鬆地掌握這些基礎知識,而不是依靠強行記憶的填鴨式的學習過程。

        因爲計算機和使用補碼最終都是保存補碼進行計算的,這樣表示的負數就總會比正數多一個數字,而 Java 也是直接使用計算機的這套規則的, 自然結果都是一樣的。

        而要是簡單回答這個問題,那麼可以簡單歸納爲:因爲原碼、反碼、補碼的規則導致了 Java 整型的負數比正數多一個數字範圍。

 

—— write by NOT IN【 無知無識 無言無聞 

 

附《十個爲什麼》系列相關文章:

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