原碼,反碼,補碼、移碼


參考文章

參考文章1

https://blog.csdn.net/zl10086111/article/details/80907428

作者:張子秋
出處:http://www.cnblogs.com/zhangziqiu/ 
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

 

參考文章2

https://blog.csdn.net/afsvsv/article/details/94553228

 

參考文章3

https://www.cnblogs.com/Jamesjiang/p/8947252.html

 

一、預備知識

在學習原碼, 反碼和補碼之前, 需要先了解一些概念.

1、機器數

一個數在計算機中的二進制表示形式,  叫做這個數的機器數機器數是帶符號的,在計算機用一個數的最高位存放符號, 正數為0, 負數為1.

比如,十進制中的數 +3 ,計算機字長為8位,轉換成二進制就是00000011。如果是 -3 ,就是 10000011 。

那么,這里的 00000011 和 10000011 就是機器數。

 

2、真值

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

例:

0000 0001的真值 = +000 0001 = +1

1000 0001的真值 = –000 0001 = –1

 

3、原理

我們學習之前還得認識二進制,十六進制。會二進制與十進制的相互轉化運算。

進制轉換可以參考我的博客:https://www.cnblogs.com/Zzbj/p/10905744.html

 

由計算機的硬件決定,任何存儲於計算機中的數據,其本質都是以二進制碼存儲。

根據馮~諾依曼提出的經典計算機體系結構框架。一台計算機由運算器,控制器,存儲器,輸入和輸出設備組成。其中運算器,只有加法運算器,沒有減法運算器。

 

所以,計算機中的沒法直接做減法的,它的減法是通過加法來實現的。你也許會說,現實世界中所有的減法也可以當成加法的,減去一個數,可以看作加上這個數的相反數。當然沒錯,但是前提是要先有負數的概念。這就為什么不得不引入一個該死的符號位。

  1. 而且從硬件的角度上看,只有正數加負數才算減法。

  2. 正數與正數相加,負數與負數相加,其實都可以通過加法器直接相加。

原碼,反碼,補碼的產生過程,就是為了解決,計算機做減法和引入符號位(正號和負號)的問題。

 

二、原碼, 反碼, 補碼的基礎概念和計算方法

對於一個數, 計算機要使用一定的編碼方式進行存儲. 原碼, 反碼, 補碼是機器存儲一個具體數字的編碼方式.

 

1、原碼

原碼:是最簡單的機器數表示法。用最高位表示符號位,‘1’表示負號,‘0’表示正號。其他位存放該數的二進制的絕對值。

 

例如:

帶符號的8位二進制:

[+1] = 0000 0001 = +1

[-1] = 1000 0001 = -1

 

若以帶符號位的四位二進值數為例 

  1. 1010 : 最高位為‘1’,表示這是一個負數,其他三位為‘010’,

  2. 即(0*2^2)+(1*2^1)+(0*2^0)=2(‘^’表示冪運算符)

  3. 所以1010表示十進制數(-2)。

 

下圖給出部份正負數數的二進制原碼表示法

 

首先大概了解一下二級制加法運算

二進制運算規則:逢2進1
0+0=0,0+1=1,1+0=1,1+1=10 
也就是當兩個數相加的二進制位僅一位為1時,相加的結果為1;
如果兩個二進制位全是0,相加的結果仍為0;
而如果兩個相加的二進制位均為1,則結果為10(相當於十進制中的2)


例如:
# 這里暫時不考慮符號
1010 + 0110 = 10 + 6 = 16

   1010
+  0110
----------
  10000

逢2進1(從右到左):
0+0=0
1+1=10  # 進位1,剩余0
0+1+1(這個1是進位) = 10  # 進位1,剩余0
1+0+1(這個1是進位) = 10  # 進位1,剩余0
1  # 最后進位得到的1


再比如
111 + 111 = 7 + 7 = 14

   111
+  111
----------
  1110

逢2進1(從右到左):
1+1=10  # 進位1,剩余0
1+1+1(這個1是進位)=10+1=11  # 進位1,剩余1,這種情況的運算規則是:先算原本的1+1=10,再算10+1(進位)=11
1+1+1(這個1是進位)=10+1=11  # 進位1,剩余1
1  # 最后進位得到的1

 

二進制減法運算

二進制減法如何借位
例如:100 - 1 = 4 - 1 = 3


二進制在減法運算時,當前一位為0就再向前借位。
直到不為零的位數,借1位當2算。

所以
  100
-   1

借位后相當於
  012  (實際二進制中沒有2,這里需要自己體會一下)
-   1
----------
  011

即:3

 

OK,原碼表示法很簡單有沒有,雖然出現了+0和-0,但是直觀易懂。
於是,我們高興的開始運算。

0001+0010=0011 (1+2=3)OK

0000+1000=1000 (+0+(-0)=-0) 額,問題不大

0001+1001=1010 (1+(-1)=-2)

噢,1+(-1)=-2,這仿佛是在逗我呢。

於是我們可以看到其實正數之間的加法通常是不會出錯的,因為它就是一個很簡單的二進制加法。

而正數與負數相加,或負數與負數相加,就要引起莫名其妙的結果,這都是該死的符號位引起的。0分為+0和-0也是因他而起。

所以原碼,雖然直觀易懂,易於正值轉換。但用來實現加減法的話,運算規則總歸是太復雜。於是反碼來了。

 

2、反碼

我們知道,原碼最大的問題就在於一個數加上他的相反數不等於零。

例如:

0000 0001 + 1000 0001 = 1000 0010 (1+(-1)=-2)

0000 0010 + 1000 0010 = 1000 0100 (2+(-2)=-4)

於是反碼的設計思想就是沖着解決這一點,既然一個負數是一個正數的相反數,那我們干脆用一個正數按位取反來表示負數試試。

反碼:

正數的反碼還是等於原碼 負數的反碼就是他的原碼除符號位外,按位取反。

例如:

帶符號的8位二進制:

[+1] = [00000001] = [00000001]

[-1] = [10000001] = [11111110]

 

那么我們再試着用反碼的方式解決一下原碼的問題

把原碼都轉成反碼進行計算,得到反碼,把結果得到的反碼再換算回原碼即可

反碼轉換回原碼:
正數:反碼就是原碼
負數:符號位不變,其他位按位取反

# 正數和負數相加
1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0
互為相反數相加等於0,解決,雖然是得到的結果是-0

1 - 2 = 1 + (-2) = [0000 0001]原 + [1000 0010]原 = [0000 0001]反 + [1111 1101]反 = [1111 1110]反 = [1000 0001]原 = -1
結果也是正確的

# 再試着做一下兩個負數相加
(-1) + (-2) = [1000 0001]原 + [1000 0010]原 = [1111 1110]反 + [1111 1101]反 = [1 1111 1011]反 = [1 0000 0100]原 = -4

噢,好像又出現了新問題
(-1)+(-2)=(-4)?

看來相反數問題是解決了,但是卻讓兩個負數相加的出錯了。

但是實際兩個正數相加和兩個負數相加,其實都是一個加法問題,只是有無符號位罷了。而正數+負數才是真正的減法問題.
也就是說只要正數+負數不會出錯,那么就沒問題了。負數加負數出錯沒關系的,負數的本質就是正數加上一個符號位而已。

在原碼表示法中兩個負數相加,其實在不溢出的情況下結果就只有符號位出錯而已(1001+1010=0011)

實際反碼表示法其實已經解決了減法的問題,他不僅不會像原碼那樣出現兩個相反數相加不為零的情況,而且對於任意的一個正數加負數的計算結果是正確的。
所以反碼與原碼比較,最大的優點,就在於解決了減法的問題。

但我們還有一個負數相加的問題,此時就需要用補碼了。

 

3、補碼

補碼:

  1. 正數的補碼等於他的原碼
  2. 負數的補碼是在其原碼的基礎上, 符號位不變, 其余各位取反, 最后+1. (即在反碼的基礎上+1)

 

[+1] = [00000001] = [00000001] = [00000001]

[-1] = [10000001] = [11111110] = [11111111]

 

補碼運算規則是:

  • X+Y = [X]補 + [Y]補 = [X+Y]補 = 再轉換為原碼即可
  • X-Y = [X]補 + [-Y]補 = [X-Y]補 = 再轉換為原碼即可
  • -X-Y = [-X]補 + [-Y]補 = [-X-Y]補 = 再轉換為原碼即可

例如:

正數和負數相加:
1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]補 + [1111 1111]補 = [1 0000 0000]補 = [0 0000 0000]原 = 0

兩個負數相加:
(-1) + (-2) = [1000 0001]原 + [1000 0010]原 = [1111 1111]補 + [1111 1110]補 = [1 1111 1101]補 = [1 1111 1100]反 = [1 0000 0011]原 = -3

# 由此可見,補碼即解決了符號問題,也解決了負數加負數的情況

 

4、移碼

移碼(又叫增碼或偏置碼)通常用於表示浮點數的階碼,其表示形式與補碼相似,只是其符號位用“1”表示正數,用“0”表示負數,數值部分與補碼相同。

即:不管正負數,只要將其 補碼的符號位取反即可。
例如:
[X]原= 1010_1011 ,[X]反=1101_0100,[X]補=1101_0101,[X]移=0101_0101
 

三、溢出

1、溢出的常用處理方法

用變形補碼進行雙符號位運算

使用雙符號位法計算需要遵循以下兩個規則:

  1. 兩個符號位都看做數碼一樣參加運算;
  2. 兩數進行以2^n+2為模的加法,即最高符號位上產生的進位要丟掉;
  3. 計算結果以符號位高位為結果符號
符號位 結果
00 正數
01 溢出
10 溢出
11 負數

 

2、溢出解析

1.說到溢出,還是要先提一下自然丟棄。
請看下面這個例子:

  -3 - 6

  -3  -->   1101[補]

  -6  -->   1010[補]

  相加  --------

  結果 (1)0111

  結果的位數比原先的多出了一位,此處最左邊的1,是會被自然丟棄的(就是不要了)。再看結果,對0111[補]=0111[原](也就是+7)。這和我們想要的-9有天壤之別。為什么會出現這個情況呢?

  原因就是這里出現了溢出!

首先來看溢出的定義:

  對一個N位有符號的二進制補碼,其可以表達的范圍是 [ - 2N-1 ~ 2N-1 - 1 ] 之間。如果超出這個范圍就稱為溢出了。

拿上面的-3-6來說,我們剛剛在計算時,轉換為四位二進制補碼(算上符號位),那么它的取值范圍是-8~+7之間。而我們想要的結果是-9,比范圍的最小值還要小,這個叫做負溢出。同理如果想要的結果比最大值還要大,那么就叫做正溢出,如取值范圍是-8~+7之間,想要的結果是+9,那么就是正溢出。

說完了溢出的定義,我們來說說溢出的判定,就是怎么在計算開始時知道自己算的結果是不是溢出了?

采用雙符號形式,我們再計算一次 -3-6
    11 101[補]

    11 010[補]

(1)10 111[補]

得到結果是 10 111[補](最高位超出位寬,自然舍棄),采用雙符號運算得到的最后結果應該是單符號的,

就是:-3 = 11 101 前面雙符號11代表負數, -6 = 11 010 前面雙符號11代表負數,但是得到的結果10 111(發生負溢出),只有前面1是符號位,代表負數,真值是0 111

因此最后結果:10 111[補] = 1 0110[反] = 1 1001[原] = -9

 

四、反碼與補碼的原理

計算機中的數值是以補碼形式存儲的(只不過正數的補碼跟原碼一樣。

強調原碼,反碼,補碼的引入是為了解決做減法的問題。在原碼,反碼表示法中,我們把減法化為加法的思維是減去一個數,等於加上一個數的相反數,結果發現引入了符號位,卻因為符號位造成了各種意向不到的問題。

反碼+1 ,它只是補碼的另外一種求法,不是補碼的定義。

 

1、補碼的原理

為了便於理解可以用時鍾計算(12小時制的)
9要撥到5,可以減4,也可以加8 ,所以此時 -4和+8是等價的 。
那么 ,我們可以認為 8是4的補碼
那么這兩個數有什么聯系呢? 沒錯他們和在一起就繞了時鍾一圈,用數學表達的話就是 兩數的相加的絕對值 為 12
類比到補碼:首先的明白一圈是多少 ?假設機器一圈為128(2的7次方)
於是用128減真值 也就是所謂的 數值位取反,末位加一 的操作了。
再回到時鍾, 9-4=9+[4]補=(9+8)%12 因為他會多走一圈,所以我們再在這里還要 %12,而計算機補碼是有周期的,不用這一步。

 

也就是,當前9點,我們希望撥到5點,做法如下

1. 往回撥4個小時: 9 - 4 = 5

2. 往前撥8個小時: (9 + 8) mod 12 = 5

3. 往前撥8+12=20個小時: (9+20) mod 12 =5

2,3方法中的mod是指取模操作, 17 mod 12 =5 即用17除以12后的余數是5.

所以鍾表往回撥(減法)的結果可以用往前撥(加法)替代!

現在的焦點就落在了如何用一個正數, 來替代一個負數. 上面的例子我們能感覺出來一些端倪, 發現一些規律. 但是數學是嚴謹的. 不能靠感覺.

首先介紹一個數學中相關的概念: 同余

兩個整數a,b,若它們除以整數m所得的余數相等,則稱a,b對於模m同余

記作 a ≡ b (mod m)

讀作 a 與 b 關於模 m 同余。

舉例說明:

4 mod 12 = 4

16 mod 12 = 4

28 mod 12 = 4

所以4, 16, 28關於模 12 同余.

 

負數取模

-1 mod 4 = (-1 + 4*n) mod 4,n取正整數,一直到括號里的數不為負數。

例如:

(-2) mod 12 = 12-2=10

(-4) mod 12 = 12-4 = 8

(-5) mod 12 = 12 - 5 = 7

 

再回到時鍾的問題上:

回撥2小時 = 前撥10小時

回撥4小時 = 前撥8小時

回撥5小時= 前撥7小時

注意, 這里發現的規律!

結合上面學到的同余的概念.實際上:

(-2) mod 12 = 10

10 mod 12 = 10

-2與10是同余的.

(-4) mod 12 = 8

8 mod 12 = 8

-4與8是同余的.

距離成功越來越近了. 要實現用正數替代負數, 只需要運用同余數的兩個定理:

反身性:

a ≡ a (mod m)

這個定理是很顯而易見的.

線性運算定理:

如果a ≡ b (mod m),c ≡ d (mod m) 那么:

(1)a ± c ≡ b ± d (mod m)

(2)a * c ≡ b * d (mod m)

如果想看這個定理的證明, 請看:http://baike.baidu.com/view/79282.htm

所以:

7 ≡ 7 (mod 12)

(-2) ≡ 10 (mod 12)

7 -2 ≡ 7 + 10 (mod 12)

現在我們為一個負數, 找到了它的正數同余數. 但是並不是7-2 = 7+10, 而是 7 -2 ≡ 7 + 10 (mod 12) , 即計算結果的余數相等.

接下來回到二進制的問題上, 看一下: 2-1=1的問題.

2-1=2+(-1) = [0000 0010] + [1000 0001]= [0000 0010] + [1111 1110]

先到這一步, -1的反碼表示是1111 1110. 如果這里將[1111 1110]認為是原碼, 則[1111 1110]原 = -126, 這里將符號位除去, 即認為是126.

發現有如下規律:

(-1) mod 127 = 126

126 mod 127 = 126

即:

(-1) ≡ 126 (mod 127)

2-1 ≡ 2+126 (mod 127)

2-1 與 2+126的余數結果是相同的! 而這個余數, 正式我們的期望的計算結果: 2-1=1

所以說一個數的反碼, 實際上是這個數對於一個膜的同余數. 而這個膜並不是我們的二進制, 而是所能表示的最大值! 這就和鍾表一樣, 轉了一圈后總能找到在可表示范圍內的一個正確的數值!

而2+126很顯然相當於鍾表轉過了一輪, 而因為符號位是參與計算的, 正好和溢出的最高位形成正確的運算結果.

既然反碼可以將減法變成加法, 那么現在計算機使用的補碼呢? 為什么在反碼的基礎上加1, 還能得到正確的結果?

2-1=2+(-1) = [0000 0010] + [1000 0001] = [0000 0010] + [1111 1111]

如果把[1111 1111]當成原碼, 去除符號位, 則:

[0111 1111] = 127

其實, 在反碼的基礎上+1, 只是相當於增加了膜的值:

(-1) mod 128 = 127

127 mod 128 = 127

2-1 ≡ 2+127 (mod 128)

此時, 表盤相當於每128個刻度轉一輪. 所以用補碼表示的運算結果最小值和最大值應該是[-128, 128].

但是由於0的特殊情況, 沒有辦法表示128, 所以補碼的取值范圍是[-128, 127]

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM