知多一點二進制中的負數

hello~親愛的看官老爺們新年好~相信很多同窗知道,若是要將一個數字轉換爲它的相反數,在 Javascript 中,除了在它前面加個-號以外,還能夠對該數字進行取反,以後再加 1。前者(本質是 0 減去對應的數字)能夠獲得相反數,徹底符合咱們的直覺,但爲什麼取反加一也能夠,這看起來不太科學,本文將帶你一探究竟~編碼

友情提示,計算機科班的童鞋,對此應該是爛熟於心了,可能對你幫助有限。但不清楚其中細節的同窗,但願本文能知足你好奇心之餘,瞭解多一點二進制的知識。如下是正文~spa

從減法開始

估計各位童鞋確定聽過如下這條法則:code

減去一個數,等於加上這個數的相反數。ip

同時也應該瞭解,爲了區分正負數,二進制中的最高位是符號位,其中正數的符號位是 0,而負數的符號位爲 1。以 Java 的 byte 類型(8位,範圍是 -128 ~ 127,即 0000000011111111)爲例,若是按照直覺,既然最高位爲符號位,那麼 -1 應該表示爲 10000001。想法很美好,現實卻很骨感。get

思考如下問題,儘管負數不肯定,但咱們確定正數 1 表示爲 00000001,若是須要獲得算式 1-1 的結果 ,按照上面的法則,能夠將算式轉化爲 1+(-1) 進行運算,可是 10000001 + 00000001,不管怎麼進行運算,彷佛都很可貴到 00000000 這個值吧?因而可知,計算機中儲存負數的值,並非那麼簡單的~數學

撥動時鐘

負數的探索彷佛陷入了死衚衕,那讓咱們先把目光轉移一下,從屏幕轉到牆上的時鐘之上~假設這個鍾時針和分針能夠分別進行調整,互不影響,且如今時鐘顯示的時間是 10:40,但對比北京時間,快了 10 分鐘,那麼咱們改如何調整時鐘呢?簡單地作個算式:it

45
-   10
----------
    35
複製代碼

得出的答案是 35,那麼將分針調整到 35 的位置便可,也不須要調整時針。很簡單對吧?那若是如今時間也是 10:40,但時間快了 15 分鐘,那該如何調整?你心中估計會想,那還不簡單,豎式同樣能算出來~但這裏加一個需求,不但願豎式中出現借位(也就是 運算 40-15 時,因爲被減數個位不如減數個位大,須要被減數從十位中借 1 到個位之上),那該如何實現呢?table

emmmmmmmm,彷佛比較麻煩~借位是因爲被減數的某一位不夠減數大而致使的,那若是被減數足夠大,彷佛就能解決這問題。原式是 40-15,能夠等價改寫爲 40+(59-15)-59,答案是徹底一致的。但這裏仍是有問題,運算兩次以後,剩下的算式爲 84-59,仍然要借位不是嗎?那咱們再改寫一下:40+(59-15)+1-60。這樣的算式運算下來,再也不須要借位了。換成時鐘操做,就是直接將分針調整到 25 便可。class

看到這裏,估計你心中會多了一點明悟,但彷佛其中還有一些說不過去的地方對吧?好比這個例子:如今的時間是 10:15,如今的時間比北京時間快了 40 分鐘,那該如何進行運算呢?按照上面的例子,能夠依樣寫下這條算式 15+(59-40)+1-60,但問題來了,算到最後,是 35-60,仍是要借位不是嗎?硬件

是,但能夠不是。-60 在時鐘上的本質是,將時針回撥一下。那麼 35-60,其實能夠分解爲兩個操做,時針撥動到 35 的位置,時針回撥到 9 的位置,如今的時間爲 09:35,難道不是正確的答案嗎?

不那麼同樣的數軸

在恍然大悟前先停一下,還有一點點東西須要瞭解。相信數軸的概念銘刻在各位心中,它是徹底符合直覺的,且數軸是無窮的,相信你們也都知道,通常印象中的數軸以下:

-60, -59, -58, -57···, -2, -1, 0, 1, 2···57, 58, 59

但若是數軸是有範圍的話,假設總共只有 120 個整數在數軸上,若是咱們想消除負號,那麼用正數表示負數,也何嘗不可,好比以 59 爲分界,大於 59 的數均爲負數。即 -1 表示爲 119,-40 表示爲 80,-25 表示爲 95 等:

60, 61, 62, 63···118, 119, 0, 1, 2···57, 58, 59

那麼以前時鐘的例子,是以 59 做爲避免借位的被減數,但這是爲了方便時鐘撥動,這裏咱們再往前跨一步,剛纔時鐘的運算:15-40 能夠表示爲 15+80,運算結果是 95,對錶查詢可得,95 的值表明的是 -25,運算正確!

你可能會吐槽減法是借位了,但主要是爲了方便映照時鐘的例子,換成 999 做爲避免借位的被減數,就是標準對 9 的補數。不妨在紙上畫一下,此時 -1 表示爲 999,-40 表示爲 959,-25 表示爲 974,15-40015+959,運算結果是 974,也是徹底對應上的。

重回二進制

有了上面的鋪墊,二進制的負數已經呼之欲出啦~符號位自然能夠做爲正負數的分割點。仍是以 Java 的 byte 類型爲例,8 位一共能夠表示 256 個數字,因爲 0 表示爲 00000000,首位符號位也是 0,於是正數少一位,按照上一節的例子,推出當前序列爲:

10000001, 10000002···11111101, 11111110, 11111111, 00000000, 00000001···01111101, 01111110, 01111111

二進制數 十進制數
10000000 -128
10000001 -127
10000002 -126
··· ···
11011000 -40
··· ···
11100111 -25
··· ···
00001111 15
··· ···
01111111 127

那麼仍是這條算式:15-40,二進制中即爲 00001111+11011000,稍微數一下手指,得出答案是 11100111,對應的值爲 -25!然而,這個表背起來是沒意義的,那該如何運算呢?思考一下,以前運算 15-40 時,咱們轉換爲 15+(59-40)+1-60,59 爲足夠大的被減數,在 byte 類型中,最大的數不會超過 11111111(即 255),於是能夠轉換爲:

11111111(255)
-   00101000(40)
+   00000001(1)
+   00001111(15)
-  100000000(256)
複製代碼

二進制其實十分有趣,先觀察首先須要運算的 11111111-00101000,結果是 11010111,也就是各位取反,下一步是加一,獲得結果是 11011000,不就是表中 -40 對應的值了麼?因此該表的推導是有嚴格的數學意義的,並非隨便編造一個表,到這裏,應該明白爲什麼在計算機中,取某個數的相反數是取反再加一了吧~

算式最後一步是減去 256,計算機中這步均可以省下了,由於 256 已經超過 byte 的範圍,直接忽略。同理,若是計算如 -1+1 之類的算式時,會獲得答案是 256 (即 100000000),但因爲超過範圍,首位 1 直接被忽略不計,於是得出的答案是 0 。以上運算,符號位均參與運算,並不須要區別對待,這對計算機而言是很是友好的。

小結

爲嚴謹起見,補充兩個條件:

  1. 運算數均爲整數。
  2. 計算結果均不溢出。

以上就是全文的內容啦,二進制相關的知識,實際上是至關有意思的,也許瞭解它們並不會使咱們的代碼能力日新月異,但保持好奇心,不斷探索未知的領域,必定是一個良好的習慣~

感謝各位看官大人看到這裏,知易行難,但願本文對你有所幫助~謝謝!

參考資料

補碼

《編碼:隱匿在計算機軟硬件背後的語言》

程序是怎樣跑起來的

相關文章
相關標籤/搜索