2的補數
2的補數,把負數變成2進位0和1的方法。
電腦儲存負數也是儲存2的補數。
電腦計算的方式都是用2的補數來計算。
名詞介紹
英文 | 中文 |
Sign-and-Magnitude | 符號數值表示法 |
1’s complement | 1的補數 |
2’s complement | 2的補數 |
符號數值表示法
「符號數值表示法」就是有正負號的二進位。
把最左邊的第1個bit當作正負號,1為負號,0為正號,剩下的bit就跟原本二進位一樣。
1的符號數值表示法 = 00000001
-1的符號數值表示法 = 10000001
1的補數
把整數1的二進位00000001,0與1互換,就是1的補數。
00000001
↓
11111110
2的補數
1的補數 + 1 = 2的補數
11111110
+ 1
-----------
11111111
11111111 為2的補數。
以上是+1變成-1(2的補數)的過程,電腦負數的計算與儲存,都是存2的補數。
正整數
正整數的二進位與「符號數值表示法」、「1的補數」、「2的補數」都一樣,不用再轉換。
正整數最左邊第1個bit是0。
1 符號數值表示法 = 00000001
1 1的補數 = 00000001
1 2的補數 = 00000001
轉成2的補數
負數就是2的補數,負數不能再轉2的補數。
只有正整數才能轉2的補數。
要把正整數1變成負-1的過程如下:
00000001
↓
0與1互換
↓
11111110
↓
加1
↓
11111111
2的補數轉回符號數值表示法
英文是Convert 2’s complement to original binary.
什麼時候要把2的補數轉回「符號數值表示法」?
若計算結果是負數(最左邊第1個bit是1),負數本身就是2的補數,但電腦顯示負號是用「符號數值表示法」也就是有正負號的二進位來顯示,不是顯示2的補數。
如何轉回符號數值表示法?
以下是正整數變成負數(2的補數)的過程:
2進位 -> 0與1互換 -> 加1 -> 2的補數
2的補數轉回符號數值表示法,倒回來計算,以下請注意箭頭方向。
符號數值表示法 <- 最左邊第1個bit不變,其它0與1互換 <- 減1 <- 2的補數
2的補數 11111111
↓
減1
↓
1111110
↓
最左邊第1個bit不變,其它0與1互換
↓
10000001
10000001
注意!左邊第1個bit是正負號,0代表正整數,1代表負數。
後面7個bit(0000001)才是代表數值1。
二進位減法
若尾數為0,二進位減法技巧。
從右向左,第一個出現的1變成0,後面所有的0變成1。
10000010
- 1
------------
10000001
10000100
- 1
------------
10000011
10001000
- 1
------------
10000111
10101000
- 1
------------
10100111
範例1
1
System.out.println(~ 2);
2的2進位 = 00000010
~ 運算子代表0與1互換。
00000010
↓
~ 0與1互換
↓
11111101
11111101是一個負數,第1個bit是1,是2的補數。
2的補數要轉回「符號數值表示法」,請注意以下箭頭方向。
符號數值表示法 <- 最左邊第1個bit不變,其它0與1互換 <- 減1 <- 11111101(2的補數)
11111101
↓
2的補數減1
↓
11111100
↓
第1個bit不變,其它0與1互換
↓
10000011
程式碼
1
2
3
4
5
public class Test {
public static void main(String[] args) {
System.out.println(~ 2);
}
}
-3
執行結果就是-3
2的補數變成程式
要如何寫2的補數的程式碼呢?
前一個例子的2的補數11111101
java程式碼
1
2
3
4
5
6
7
8
public class Test {
public static void main(String[] args) {
// 2的補數
byte b = (byte) 0b11111101;
// 會自動轉回符號數值表示法
System.out.println(b);
}
}
-3
c++程式碼
1
2
3
4
5
6
7
8
9
#include <iostream>
using namespace std;
int main() {
// 2的補數
int8_t b = 0b11111101;
// 會自動轉回符號數值表示法
cout << (int)b << endl;
return 0;
}
-3
計算結果為正整數
以下計算結果為正整數,也就是左邊第1個bit是0,不用轉回符號數值表示法,一開始我就有提到正整數的符號數值表示法、1的補數與2的補數都跟二進位一樣,不用再轉換。
1
System.out.println(~ -2);
根據題目,先求出-2,也就是+2的2補數(負數)。
2的二進位 = 00000010
以下是正整數變成負數的過程:
2進位 -> 0與1互換 -> 加1 -> 2的補數
00000010
↓
0與1互換
↓
11111101
↓
加1
↓
11111110
得到-2(2的補數) = 11111110
接下來~ 運算子代表0與1互換
11111110
↓
0與1互換
↓
00000001
00000001已經是正整數二進位,左邊第1個bit是0,不用再轉回符號數值表示法。
程式碼
1
2
3
4
5
public class Test {
public static void main(String[] args) {
System.out.println(~ -2);
}
}
1
位元OR運算子計算
|
位元OR運算子,有1個為1,才是1。
1
System.out.println(-2 | 3);
11111110 (-2)
or 00000011 (3)
---------
11111111
or的計算結果為負數(2的補數),第1個bit是1。
要把2的補數轉成符號數值表示法,注意以下箭頭方向。
符號數值表示法 <- 最左邊第1個bit不變,其它0與1互換 <- 減1 <- 11111111
11111111
↓
減1
↓
11111110
↓
0與1互換
↓
10000001
1
2
3
4
5
public class Test {
public static void main(String[] args) {
System.out.println(-2 | 3);
}
}
-1
執行結果為-1。
重要公式
正整數二進位變成負數(2的補數)過程:
正整數二進位
↓
0與1互換
↓
1的補數
↓
1的補數 + 1
↓
2的補數
負數(2的補數)變成「符號數值表示法」過程,也就是把上面的過程改由「由下往上」執行。
2的補數
↓
2的補數減1
↓
1的補數
↓
最左邊第1個bit不變,其它0與1互換
↓
符號數值表示法
1的補數 + 1 = 2的補數
2的補數 - 1 = 1的補數
其它注意事項
0的符號數值表示法,1的補數,2的補數都一樣
0 符號數值表示法 = 00000000
0 1的補數 = 00000000
0 2的補數 = 00000000
8bit的-128
8bit的-128,10000000是2的補數,10000000沒辦法轉回「符號數值表示法」,因為8-bit最大的正整數是127,沒有對映的128正整數。
1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
// -128
byte b = (byte) 0b10000000;
System.out.println(b);
}
}
符號數值表示法、2的補數
以下是有爭議,容易搞混淆的。
10進位 | 符號數值表示法 | 正整數二進位 | 1的補數 | 2的補數 |
---|---|---|---|---|
-1 | 10000001 | 00000001 | 11111110 | 11111111 |
-2 | 10000010 | 00000010 | 11111101 | 11111110 |
-127 | 11111111 | 01111111 | 10000000 | 10000001 |
-128 | 無法表示 | 沒有+128 | 無法表示 | 10000000 |
0 | 00000000 | 00000000 | 00000000 | 00000000 |
1 | 00000001 | 00000001 | 00000001 | 00000001 |
unsinged
unsinged的意思是沒有正負號的正整數。
C++有unsinged正整數。
Java的數字都有正負號,沒有unsinged正整數。