由 128的補碼引出的深層次思考

2021-05-26 20:55:56 字數 2890 閱讀 5472

一般的說法是負數的補碼為其原碼除符號位外取反然後總體加一,也就是說,要得到乙個負數數的補碼,要先知道這個負數的原碼才行。那麼,問題出現了,在8位長度下,-128的原碼與反碼都不存在,因為乙個位元組的有符號數的原碼範圍是: -127 ~ + 127 ,既然不存在 -128的原碼那麼就無法求出 -128 的補碼了,怎麼辦?  

其實,這個問題的實際意義是,既然說計算機內部的有符號整數都是補碼,那麼怎麼才能有效的實現這一設計呢?潛台詞是:根據上面由原碼推導出補碼的理論,如果是正數,計算機得到其原碼,也就是得到了其補碼(正數補碼等同於原碼),如果是負數,先得到其原碼,然後再取反加一就可以了。也就是說按這個思維設計編譯器,或計算機電路,就可以了。但是如果出現開始說的 -128的補碼問題,這個設計就不能工作了。其實,在真正的設計中,這種獲得負數補碼的「取反加一」的方案根本沒有實施過!

拿現代的計算機舉例,輸入裝置只有鍵盤,通過編輯器程式把鍵盤的掃瞄碼變成ascii碼儲存起來,比如輸入 『a』就儲存 0x61 ,輸入 『5』就儲存 0x35 ,這些都是ascii碼。當你想得到真正的機器數時(注意,機器數和ascii是不同的,字元『5』的ascii碼是0x35,而數字5的機器數是0x5),需要借助編譯器把表示數字的字串從ascii碼變成真正的機器數,比如你想得到 56的機器數(就是0x38),就可以輸入語句「 db 56 」,讓彙編器程式幫你把56的ascii碼字串 :「0x35, 0x36」,轉變成真正的機器數 0x38。這一轉化需要這樣一段程式:先把 『5』與 『0』做減法,就是 0x35 – 0x30 得到 0x5 (這一步就將ascii字元5變成了真正的機器數5),再把 0x5 與 0xa (就是十進位制 10)相乘得到 0x32 (就是十進位制 50) ,然後再把 『6』與 『0』做減法,就是 0x36 – 0x30 得到 0x6 (就是機器數6),最後把 0x32 與 0x6 相加,得到 0x38 ,就是機器數56了。

如果想得到 -56 ,就用語句 「 db -56 」,這裡得到機器數 56 的步驟與上面一致,只是最後要把 0xff 與 0x38 相乘,就是 -1 * 56 ,最後得到 0xc8 ,就是 -56 的補碼。

這裡我們看到,得到 -56 得補碼時根本沒用什麼「取反加一」,這裡處理的過程都是很自然的,只要考慮各個數值的運算,而不用考慮數值的補碼形式,以及如何得到負數的補碼。為什麼?因為加法,有符號乘法等指令的電路,都是按補碼輸入,補碼輸出來設計的,你只要保證輸入的是補碼,輸出的肯定也是補碼。所以,只要你輸入 -1 的補碼 0xff ,與56的補碼 0x38 ,得到的自然是 -56 的補碼 0x c8 。綜上,我們在獲得 -56 的補碼時,沒有採取先得到 -56 的原碼,然後除符號位外各位取反,最後再總體加一的方式。

由此可見,計算機是乙個相當「封閉」的系統,他內部所有的有符號整數都是補碼形式存在的,只要按數值的實際意義考慮問題,不用擔心它的儲存方式,比如想讓 -56 與 6 相乘,你根本不用擔心結果那個負數怎麼變成補碼,所有的運算電路都是按補碼設計的。換句話說,「封閉」的計算機內部的有符號數都是補碼的形式存在的,你根本不用考慮什麼原碼,什麼取反加一了,你只要考慮你想要的數值就可以了,不要擔心他怎麼儲存的。

有了上面那個ascii碼到機器數的轉換程式後,數字的輸入就不再是問題了,但是,考慮的更遠一點,如果在計算機的「蠻荒時代」,所有的指令,資料,都要用機器語言一位一位的輸入時(搬開關、打孔),那時的有符號整數又怎麼輸入呢?確實,在那個環境下,就只能用我們的大腦計算了,把數字在大腦中轉換成補碼的樣子,然後輸入。其實,真正需要我們在大腦裡轉換然後再輸入的數只有5個,分別是 『+』,『-』,『0』,0,-1 ,他們的補碼分別是:0x2b ,0x2d,0x30,0x00,0xff ,好了,用這5個補碼和機器指令編出我們剛才講的ascii碼轉化機器數的程式,從此以後,我們只要輸入數字的ascii字串就可以了,讓這個程式幫我們轉換成補碼,不用再辛苦的計算補碼了。

深入到機器層,機器層面也根本用不著「原碼取反加一」,因為機器裡的所有數字,都是人工或者是編譯器輸入的,已經轉換成了補碼了,他本身已經是乙個完備而封閉的系統了,根本沒有介面接收其他有符整數的編碼方案了。需要注意的是有乙個取補指令neg ,這裡的取補不是本來意義的「取反加一」(本來意義的「取反加一」只對負數),而是不論正負,把每一位取反,最後加一,就是說 neg 20 結果為 -20 ,neg -56 結果為56 ,就相當於把運算元與 -1 像乘了。這顯然與為得到負數的補碼採取的「取反加一」截然不同。當兩個數做減法時,比如:有符號整數 60 (0x3c) 與 有符號整數 77 (0x4d)相減,加法器有一段電路把減數77取反加一,但是,請注意,這個電路跟neg一樣,不管正負每位取反最後加一,就相當於用 乘法指令imul 把 運算元 與 -1 相乘了。這也跟求負數的補碼採取的「取反加一」截然不同。

綜上,無論在編譯器層面還是機器層面,「負數的補碼為其原碼除符號位外取反然後總體加一」這個方法都沒有用上,這只是教科書上提供的便於記憶的方法而已。  

根據上面的說法,分析下c中具體的問題:   c中int佔4個位元組,表示範圍從 – 2147483648 到 2147483647 。  

問題1:int -2147483648 ;編譯後的值正確嗎?

上面說的轉換程式,這個編譯的步驟為: 計算2*10^9 + 2*10^8+ …… +4*10+8 ,因為int只能表示到 2147483647 (0x7fff ffff),再加1的話就溢位了,得到了:0x8000 0000 ,(就是 -2147483648) ,然後再把這個結果與 -1 就是 0xffff ffff 相乘,得到的結果也溢位了,為:0x8000 0000 ,但是這恰好是 – 2147483648的補碼,所以,雖然編譯中雖然出現了兩次溢位,但得到的結果是正確的。  

問題2:int x = - 2147483648; 那麼 –x 是 2147483648嗎?

因為 – 2147483648 (0x8000 0000)與-1 (0xfff ffff)相乘結果為:0x8000 0000 就是– 2147483648 ,所以 –x 的結果還是 – 2147483648 而不是2147483648 。   所以,凡是結果可能超越表示範圍的時候,一定要慎重,因為結果可能是你意料之外的。

由 128的補碼引出的深層次思考。

一般的說法是負數的補碼為其原碼除符號位外取反然後總體加一,也就是說,要得到乙個負數數的補碼,要先知道這個負數的原碼才行。那麼,問題出現了,在8位長度下,128的原碼與反碼都不存在,因為乙個位元組的有符號數的原碼範圍是 127 127 既然不存在 128的原碼那麼就無法求出 128 的補碼了,怎麼辦?...

由 128的補碼引出的思考

1.補碼 two s complement 在計算機系統 中,數值一律用補碼來表示 儲存 主要原因 使用補碼,可以將符號位和其它位統一處理 同時,減法也可按加法來處理。另外,兩個用補碼表示的數相加時,如果最高位 符號位 有進製,則進製被捨棄。補碼與原碼 的轉換過程幾乎是相同的。2.一般的說法是負數的...

物件的深層次獲取

故心故心故心故心小故衝啊 在寫 的時候遇到乙個問題,在訪問乙個物件巢狀物件在巢狀物件,例如 var obj 獲取c的值 obj.a.c 123那麼如果只能通過obj 的方式應該如何去獲取呢?這樣獲取嗎?obj a.c 錯誤那麼如何實現obj 這樣的方式獲取呢?可以從上面可以知道obj.a.c 是可以...