公號:碼農充電站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會發生下溢出,變為最大值。
(本節完。)
推薦閱讀:
歡迎關注作者公眾號,獲取更多技術干貨。