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正整數。

results matching ""

    No results matching ""