天天看點

【C/C++基礎】02_二進制原碼、反碼與補碼之間的關系1 機器數和真值2  原碼3 反碼4 補碼5  為何要使用原碼, 反碼和補碼6 小數的二進制與十進制轉換

1 機器數和真值

1.1 機器數

一個數在計算機中的二進制表示形式, 叫做這個數的機器數。機器數是帶符号的,在計算機用一個數的最高位存放符号, 正數為0, 負數為1.比如,十進制中的數 +3 ,計算機字長為8位,轉換成二進制就是00000011。如果是-3 ,就是 10000011 。那麼,這裡的 00000011 和10000011 就是機器數。計算機中的數字是以二進制補碼的方式存儲的。

1.2 真值

因為第一位是符号位,是以機器數的形式值就不等于真正的數值。例如上面的有符号數 10000011,其最高位1代表負,其真正數值是 -3 而不是形式值131(10000011轉換成十進制等于131)。是以,為差別起見,将帶符号位的機器數對應的真正數值稱為機器數的真值。

例1:0000 0001的真值 = +000 0001 = +1,10000001的真值 = –000 0001 = –1

例2:在C語言中有以下程式段:

連結:https://www.nowcoder.com/questionTerminal/ef47c08580b24f8582b629459b224ea0

來源:牛客網

char ch=-1;

printf(“%02x,%02x”,ch,(unsigned char)ch);

則輸出的結果是:ffffffff,ff

(1) %x表示十六進制整數輸出,是以char要擴充成四位元組的int。%02x表示輸出 最少 2位,不足補0.

(2) 負數在計算機中是以補碼形式存在的,正整數的補碼是本身,負數的補碼是除符号位以外各位都取反加一,也就是如果一個32位的負數原來是1(符号位)00 ... ... 01, 取反後是1(符号位)11 ... ... 10,然後再加1,就是1(符号位)11 ... ... 11,就是32個1。是以第一個是ffff。第一個ch=-1,負數拓展到32位需要補1,是以是ffffffff.

(3) 要擴充的資料類型為無符号的 (unsigned char), 用0來填充長資料類型的高位元組,此時-1在記憶體的二進制存儲(11111111  )擴充為int即00000000 00000000 00000000 11111111(ff)。第二個ch先轉換為無符号數ff(255), 正數拓展補0,由于最少輸出2位,是以是ff

2  原碼

原碼就是符号位加上真值的絕對值, 即用第一位表示符号, 其餘位表示值. 比如如果是8位二進制:

[+1]原 =0000 0001

[-1]原 =1000 0001

第一位是符号位. 因為第一位是符号位, 是以8位二進制數的取值範圍就是:

[1111 1111 ,0111 1111],即:

[-127 , 127]

原碼是人腦最容易了解和計算的表示方式.

3 反碼

反碼的表示方法是:正數的反碼是其本身;負數的反碼是在其原碼的基礎上, 符号位不變,其餘各個位取反。

[+1] =[00000001]原 = [00000001]反

[-1] =[10000001]原 = [11111110]反

可見如果一個反碼表示的是負數, 人腦無法直覺的看出來它的數值. 通常要将其轉換成原碼再計算。

4 補碼

補碼的表示方法是:正數的補碼就是其本身;負數的補碼是在其原碼的基礎上, 符号位不變, 其餘各位取反, 最後+1. (即在反碼的基礎上+1)

[+1] =[00000001]原 = [00000001]反 = [00000001]補

[-1] =[10000001]原 = [11111110]反 = [11111111]補

對于負數, 補碼表示方式也是人腦無法直覺看出其數值的. 通常也需要轉換成原碼在計算其數值.

5  為何要使用原碼, 反碼和補碼

現在我們知道了計算機可以有三種編碼方式表示一個數. 對于正數因為三種編碼方式的結果都相同:

[+1] =[00000001]原 = [00000001]反 = [00000001]補

是以不需要過多解釋. 但是對于負數:

[-1] =[10000001]原 = [11111110]反 = [11111111]補

可見原碼, 反碼和補碼是完全不同的. 既然原碼才是被人腦直接識别并用于計算表示方式, 為何還會有反碼和補碼呢?

首先, 因為人腦可以知道第一位是符号位, 在計算的時候我們會根據符号位, 選擇對真值區域的加減.  但是對于計算機, 加減乘數已經是最基礎的運算, 要設計的盡量簡單. 計算機辨識”符号位”顯然會讓計算機的基礎電路設計變得十分複雜! 于是人們想出了将符号位也參與運算的方法. 我們知道, 根據運算法則減去一個正數等于加上一個負數, 即: 1-1 = 1 + (-1) = 0 , 是以機器可以隻有加法而沒有減法, 這樣計算機運算的設計就更簡單了.

于是人們開始探索 将符号位參與運算, 并且隻保留加法的方法. 

(1)首先來看原碼。計算十進制的表達式: 1-1=0

1 - 1 = 1 +(-1) = [00000001]原 + [10000001]原 = [10000010]原 =-2

如果用原碼表示, 讓符号位也參與計算, 顯然對于減法來說, 結果是不正确的.這也就是為何計算機内部不使用原碼表示一個數.

(2)為了解決原碼做減法的問題, 出現了反碼。計算十進制的表達式:

1-1=0

1 - 1 = 1 +(-1) 

= [0000 0001]原 + [1000 0001]原 

= [0000 0001]反 + [1111 1110]反 

= [1111 1111]反 = [1000 0000]原 

= -0

發現用反碼計算減法, 結果的真值部分是正确的. 而唯一的問題其實就出現在”0”這個特殊的數值上. 雖然人們了解上+0和-0是一樣的, 但是0帶符号是沒有任何意義的. 而且會有[0000 0000]原和[10000000]原兩個編碼表示0.

(3)補碼的出現, 解決了0的符号以及兩個編碼的問題:

1-1 = 1 +(-1) 

= [0000 0001]原 + [1000 0001]原 

= [0000 0001]補 + [1111 1111]補 

= [0000 0000]補=[0000 0000]原

這樣0用[0000 0000]表示, 而以前出現問題的-0則不存在了.而且可以用[1000 0000]表示-128:

使用補碼, 不僅僅修複了0的符号以及存在兩個編碼的問題, 而且還能夠多表示一個最低數. 這就是為什麼8位二進制, 使用原碼或反碼表示的範圍為[-127, +127], 而使用補碼表示的範圍為[-128, 127].

因為機器使用補碼, 是以對于程式設計中常用到的32位int類型, 可以表示範圍是: [-2^31, 2^31-1] 因為第一位表示的是符号位.而使用補碼表示時又可以多儲存一個最小值.

6 小數的二進制與十進制轉換

6.1  十進制轉二進制

整數和小數分别轉換:

(1)整數除以2,商繼續除以2,得到0為止,将餘數逆序排列。

(2)小數乘以2,取整,小數部分繼續乘以2,取整,得到小數部分是0為止,将整數順序排列。

例如:22.8125轉二進制

整數除以2,商繼續除以2,得到0為止,将餘數逆序排列。

22 / 2  11 餘0

11/2     5  餘 1

5 /2      2  餘 1

2 /2      1  餘 0

1 /2      0  餘 1

是以22的二進制是10110

小數乘以2,取整,小數部分繼續乘以2,取整,得到小數部分0為止,将整數順序排列。

0.8125x2=1.625 取整1,小數部分是0.625

0.625x2=1.25 取整1,小數部分是0.25

0.25x2=0.5 取整0,小數部分是0.5

0.5x2=1.0 取整1,小數部分是0,結束

是以0.8125的二進制是0.1101

十進制22.8125等于二進制10110.1101

1.2 二進制轉十進制

使用按權展開求和法,小數點左邊是2的正數次方,從0開始;小數點右邊是2的負數次方,從-1開始。 

例如将101.111(2)轉換成十進制數

                1*(2^2)+0*(2^1)+1*(2^0)      # 整數部分

  +1*(2^(-1))+1*(2^(-2))+1*(2^(-3))       # 小數部分

                                            =5.875