公號:碼農充電站pro
主頁:https://codeshellme.github.io
計算機最基本的工作是處理資料,而資料的最底層表現形式是二進位制,並非是我們人類熟悉的十進位制。可以這麼認為,計算機其實是很“笨的”,它只理解二進位制資料。
今天,主要介紹計算機是怎樣做加減運算的。你可能會想,加減運算?這麼簡單的事情,還用介紹?也許還真不是你想的那樣。
計算機的運算是由CPU 完成的,而CPU 只會做加法運算,不會做減法運算,那計算機怎樣完成減法工作呢?
1,二進位制數
我們先來看看二進位制數。
二進位制數是由0,1 組成的,比如:
- 十進位制的5,用二進位制表示是 101。
- 十進位制的7,用二進位制表示是 111。
數字由正數和負陣列成。為了表示正負數,計算機中就有了有符號數和無符號數之分:
- 無符號數:英文為
unsigned
,只能表示正數。 - 有符號數:英文為
signed
,即能表示正數,又能表示負數。
C/C++ 語言中的數字有有符號數和無符號數之分。
Java 語言所有的數字都是有符號數。
假如,我們用 4 位二進位制,來表示無符號數,也就是隻表示正數,能表示的範圍是 0 到 15
,轉換關係如下表:
十進位制數 | 二進位制數 | 十進位制數 | 二進位制數 |
---|---|---|---|
0 | 0000 | 8 | 1000 |
1 | 0001 | 9 | 1001 |
2 | 0010 | 10 | 1010 |
3 | 0011 | 11 | 1011 |
4 | 0100 | 12 | 1100 |
5 | 0101 | 13 | 1101 |
6 | 0110 | 14 | 1110 |
7 | 0111 | 15 | 1111 |
有符號數,即要表示正數,也要表示負數。
要用二進位制表示有符號數,需要用二進位制的最高位來表示符號,0
表示正
,1
表示負
。所謂的最高位,也就是最左邊那一位。
用 4 位二進位制,來表示有符號數,能表示的範圍是 -8 到 7
,轉換關係如下表:
十進位制數 | 二進位制數 | 十進位制數 | 二進位制數 |
---|---|---|---|
0 | 0 000 |
-8 | 1 000 |
1 | 0 001 |
-1 | 1 001 |
2 | 0 010 |
-2 | 1 010 |
3 | 0 011 |
-3 | 1 011 |
4 | 0 100 |
-4 | 1 100 |
5 | 0 101 |
-5 | 1 101 |
6 | 0 110 |
-6 | 1 110 |
7 | 0 111 |
-7 | 1 111 |
上表中的最高位的符號位,已標紅。
要注意,對於有符號的4 位二進位制 ----
1000
不是-0
,而是-8
。
可以總結出,對於N
位的二進位制數:
- 無論是表示有符號數還是無符號數,都能表示
2^N
個數字。 - 若用於表示無符號數,則能表示的範圍是
[0, 2^N - 1]
。 - 若用於表示有符號數,則能表示的範圍是
[-2^(N-1), 2^(N-1) - 1]
。- 需要注意,在有符號數中,對於符號位是
1
,後面N-1
位全是0
,這種情況表示的是-2^(N-1)
(也就是所能表示的最小值),而不是-0
。 - 實際上是將
-0
這種情況解釋成了最小值
,否則就會出現+0
和-0
兩個0
。
- 需要注意,在有符號數中,對於符號位是
2,二進位制原碼
上面介紹到的二進位制就是原碼形式。
原碼就是除符號位外的其他位,儲存該二進位制數的絕對值。
用原碼進行加法計算
計算機中的數字運算都會先轉成二進位制數再進行計算。
我們用原碼來計算加法,用4 位二進位制數來計算 3 + 2
,過程如下:
可以看到,用原碼計算加法是沒有問題的。
用原碼進行減法計算
我們再用原碼來計算減法,因為CPU 只會計算加法,所以計算減法時,會將減法轉換成加法。
比如,用4 位二進位制數來計算計算 3 - 2
,會將其轉換成 3 + (-2)
, 過程如下:
可以看到 3-2
計算出來的結果是 -5
,顯然是錯誤的。
所以,用二進位制原碼來計算減法是行不通的。實際上,計算機計算減法用的是補碼。
在介紹補碼之前,我們先來看下什麼是溢位。
3,數字溢位
計算機中數字的表示是需要記憶體空間的,不同型別的數字所能佔用的空間是不一樣的。
比如,在Java 語言中short
型別佔用 2 個位元組,int
型別佔用 4 個位元組。
一個位元組等於 8 位。
既然空間大小是有限制的,所以計算機中的數字也是有範圍的,即上限和下限,如果數字超出限制,就會產生溢位。超出上限叫上溢位,超出下限叫下溢位。而溢位的部分會直接被捨去。
就像我們在上文中介紹的,對於N
位二進位制有符號整數,所能表示的範圍是 [-2^(N-1), 2^(N-1) - 1]
。
由於溢位的部分會被捨去,那麼最大值加1,將發生上溢位,變為最小值;最小值減1,將發生下溢位,變為最大值。
我們用Java 中的int
型別來驗證,Java 中int
型別的最大、最小值分別是:
- 最大值:
Integer.MAX_VALUE
,是2147483647
。 - 最小值:
Integer.MIN_VALUE
,是-2147483648
。
用下面程式碼驗證:
System.out.println(Integer.MAX_VALUE + 1 == Integer.MIN_VALUE); // true
System.out.println(Integer.MIN_VALUE - 1 == Integer.MAX_VALUE); // true
這兩行程式碼的輸出均為true
,說明最大值加1 變為最小值,最小值減1 變為最大值。
所以,在計算機中,只要一個整數的型別確定了,那麼它所能佔用的記憶體空間大小也就確定了,從而它所能表示的數字範圍也就確定了。那麼不管給這個整數加多大的數字,或者減多大的數字,最終的結果都只能在這個範圍內旋轉。
就像錶盤一樣,當錶針走過最大值的時候,就變成了最小值。
同樣,這也等同於數學中的取餘運算。只要分母確定了,不管分子是多大,或者多小的數字,最終的結果也都是在一個確定的範圍之內。
比如我們對十進位制5
進行取餘計算,那麼最終的結果都是在[0, 4]
範圍之內,如下:
0 % 5 = 0
2 % 5 = 2
397 % 5 = 2
99999 % 5 = 4
可以總結出,對數字N
進行取餘,N >= 2
且為整數,那麼結果都在 [0, N-1]
範圍之內。
4,二進位制反碼與補碼
知道了溢位,就可以介紹CPU 如何計算減法了。CPU 的減法運算使用了二進位制補碼,補碼實際上就是採用了溢位的原理。
我們直接給出反碼與補碼的定義:
- 反碼定義:正數的反碼等於其原碼,負數的反碼是其原碼除符號位外,按位取反。
- 補碼定義:正數的補碼等於其原碼,負數的補碼是其反碼加1。
比如下面的幾個數字:
十進位制數 | 2 | 3 | -2 | -5 |
---|---|---|---|---|
二進位制原碼 | 0010 | 0011 | 1 010 |
1 101 |
二進位制反碼 | 0010 | 0011 | 1 101 |
1 010 |
二進位制補碼 | 0010 | 0011 | 1 110 |
1 011 |
我們用4
位二進位制補碼來計算 3+(-2)
,如下:
最高位的1
發生上溢位,直接被捨去,所以結果是正確的。
所以,要記住,真實的計算機中的二進位制是用補碼錶示的,而不是原碼。
5,總結
本篇文章主要介紹了:
- CPU 只能做加法,不能做減法,減法要轉成加法做計算。
- 二進位制數字有三種表示方式:
- 原碼:除符號位外的其他位,儲存該二進位制數的絕對值。
- 反碼:正數的反碼等於其原碼,負數的反碼是其原碼除符號位外,按位取反。
- 補碼:正數的補碼等於其原碼,負數的補碼是其反碼加1。
- 計算機中的數字採用二進位制補碼錶示,而不是原碼錶示。
- 補碼採用了溢位的原理。
- 計算機中的數字是有範圍限制的,超出限制會發生溢位。
- 超出上限叫做上溢位。最大值加1會發生上溢位,變為最小值。
- 超出下限叫做下溢位。最小值減1會發生下溢位,變為最大值。
(本節完。)
推薦閱讀:
歡迎關注作者公眾號,獲取更多技術乾貨。