天天看點

快速了解原碼、反碼、補碼

前言

故事是一個真實的故事,前兩天要被一位國小弟折磨死,原碼、反碼、補碼不懂就算了,講了一遍還不懂。

快速了解原碼、反碼、補碼

我搞不懂是二進制太難還是我太難了呢?你們不信?立圖為證:

快速了解原碼、反碼、補碼

他這問的給我直接問懵逼了,二進制符号位不參與運算?我怎麼聽得給我都聽糊塗了,哈哈哈,後來我就給他說了要參加運算,再後來又一個問題:

快速了解原碼、反碼、補碼

他這麼确定的眼神給我搞得都有點懵逼,都吓得我打一段代碼去驗證一下結果沒毛病,又巴拉巴拉給他講了一通。

我覺得應該可以了吧,結果在淩晨1.30的時候……

快速了解原碼、反碼、補碼

算了,算了,這孩子沒得救了,不管了。給女票滴滴打算晚安,但我也曾想起自己在原碼、反碼、補碼上的困惑。

  • 記得剛學c++的時候:這啥玩意代碼不要求,不學
  • 剛學作業系統、組成原理的時候:emumm,跳過跳過。

是以以前的我也一直沒能搞懂二進制,并且很排斥二進制:既然把方法數字都封裝好為啥還要這麼麻煩的搞二進制。但事實上二進制這個知識是無論如何也避免不了的知識點。想着要拯救更多苦于二進制或者說原碼反碼補碼的國小弟國小妹們,我得站出來做點什麼。

就先拿女朋友做小白鼠試驗一下。網友直呼内行!

快速了解原碼、反碼、補碼

以下均為小白鼠内容為了確定實驗結果呃嚴謹性對話情景就不給大家帶入了。

二進制數字

什麼是二進制?百度百科對二進制是這麼定義的:

二進制(binary)在數學和數字電路中指以2為基數的記數系統,以2為基數代表系統是二進位制的。這一系統中,通常用兩個不同的符号0(代表零)和1(代表一)來表示 [1] 。數字電子電路中,邏輯門的實作直接應用了二進制,是以現代的計算機和依賴計算機的裝置裡都用到二進制。每個數字稱為一個比特(Bit,Binary digit的縮寫)

其實二進制的01就是對應數字電路中的關開,是以在整個計算機中所有的東西都是二進制科學,但我們隻需要研究數字類型的二進制的關系這個也是最簡單的,因為二進制本身就能代表數字。

如果說不談啥原碼、反碼、補碼,光光看二進制跟十進制的關系,也不考慮位數,我想大部分人可以搞得懂。

  • 比如2的二進制:

    10

    ,3的二進制:

    11

    ,4的二進制:

    100

    ,5的二進制:

    101

    .
  • 負數的二進制怎麼搞?-2二進制

    -10

    ?-3二進制

    -11

    ?這樣不太妥吧,怎麼跟着這麼一個負數?(問題一)

另外,這種不确定長度的二進制如果是一個數組我該怎麼在計算機記憶體中找的到 (問題二) ?

以一個可能不太恰當的圖展示一下:

快速了解原碼、反碼、補碼

大家一看直呼這樣不行,是以在計算機數值類型設計之初就明确表示:計算機基本資料是定長的,并且有兩部分組成:符号位(一位)和數值位(若幹位),其中符号位的0或者1分别表示正和負數,而數值位就是表示資料的大小。

你可能直呼:到底多少位表示一個數字呢?如果太長位資料如果很小(前面都是0)就會造成浪費,造成記憶體浪費,而位數太短又會導緻裝不下,網友們直呼真難。

偉大的設計者們當然考慮了這個問題,他們将數值二進制的長度分為不同長度供你使用,在java中有這8種基本資料類型(1byte=8bit):

基本類型 長度(byte) 包裝類型 取值範圍
byte 1 Byte -128~127
short 2 Short -32768 ~ 32767
int 4 Integer -2147483648~2147483647
long 8 Long -9223372036854774808~9223372036854774807
float Float 3.402823e+38~1.401298e-45(e+38 表示乘以10的38次方,而e-45 表示乘以10的負45次方)
double Double 1.797693e+308~4.9000000e-324
char Character
boolean 官方未确定 Boolean true false

比如說你byte a=1,他在記憶體中就是這樣的:

0000 0001

如果你 int b=1;因為int是32位,那麼他在記憶體中是這樣存的:

00000000 00000000 00000000 00000001

你可能會問為啥沒講負數?别急慢慢來,下面原碼、反碼、補碼講着呢。另外需要注意的是,二進制進行加法如果溢出,溢出部分不會記錄,隻會儲存有效部分,是以選用什麼資料類型也要掂量目标資料的大小範圍。

原碼

上面既然初步知道了二進制數字的一些規律,那麼就讓它來的更猛烈一些吧。原碼是什麼意思呢?

  • 原碼就是二進制的初始表示符号位,即最高位為符号位:正數該位為0,負數該位為1(0有兩種表示:+0和-0),其餘位表示數值的大小。
快速了解原碼、反碼、補碼

是不是很直接明了的展示一個值?原碼的優勢就是比較明顯的表示一個值。能夠清楚的知道這個二進制數表示是多少,簡單直覺。

但我們是否就可以使用原碼暢通無阻了呢?

當然不可以,原碼雖然可以很容易的表示一個正負數,但是我們觀察它的加法:

快速了解原碼、反碼、補碼

正數相加沒問題,但是負數的加法就出問題了:負數的加法隻考慮絕對值數值的增加而未考慮負數的特性。而負數加負數的絕對值相反,是以在原碼上負數的加法就成了一個難題,走不通。

反碼

負數的原碼無法實作加法,因為原碼如果進行加法實作的是與符号無關數值絕對值的加法。是以這點和負數的加法規則沖突,并且計算機也隻會加法。咱們隻能另從它計。

此時,有些偉大的大佬就發現了反碼這個東西,而反碼的定義是這樣的:正數的反碼與其原碼相同;負數的反碼是對正數逐位取反,符号位保持為1. 因為負數原碼的加法是相反的(即加一變成減一的操作),我們想着如果給負數原碼中的數字01颠倒那麼這個數字就會有比較有趣的事情。

  • 原碼中本來比較大的數字(-1,-2等)在這樣轉換後看起來變得很小。原本很小的數字經過這樣的轉換後看起來很大。(也就無法直覺一下看出這個數字是多少)
    快速了解原碼、反碼、補碼
  • 轉換後的數字進行加法(正數)運算,在進行01互換之後可以進行正常加法的邏輯。
    快速了解原碼、反碼、補碼
  • 負數相加好像看起來也沒問題。

但是真的就可以了嘛?正數負數用反碼表示可以暢通無阻了?no no no。咱們記得原碼中有+0,-0.但是不影響操作吧。看看反碼中+0,-0的情況:

快速了解原碼、反碼、補碼

你看看,反碼它也不行啊,what should I do?看下面的補碼分析。

補碼

反碼為啥會出現這個問題呢?主要是正負0占了兩個坑:

快速了解原碼、反碼、補碼

也就是如果你用反碼表示這個數,用它進行加法運算,正數範圍内玩沒問題,負數範圍内玩也沒問題,但是當你從負數邁到正數的時候會經過兩個0(-0,+0)兩個零重複表示了。

這該如何表示呢?我們看看這些數字反碼的規律:

**-3的反碼: 1111 1100

-2的反碼: 1111 1101

-1的反碼: 1111 1110

-0的反碼: 1111 1111**

+0的反碼:0000 0000

這些負數的反碼,如果都能加個1,那麼這樣正負0的沖突不久不存在了嘛?!!這就是所謂的補碼:符号位不變,正數的補碼為和原碼、反碼一緻,負數的補碼為其反碼加1.

快速了解原碼、反碼、補碼

這樣我們就解決了所有難題,叱咤風雲的進行計算了,其實我們在計算機中二進制也是用補碼表示所有數值。

對于補碼,你确實無法直接看出它是多少,負數或許了解起來可能還有那麼一點點抽象,我們該如何了解補碼呢?

我是這麼了解的:二進制數把資料分為正負兩個部分,分别表示兩個區間:

快速了解原碼、反碼、補碼

什麼意思呢?這個也就是說你可以把負數看成一部分,正數看成一部分。而每個部分的數值也是相同的:無論負數還是正數出去符号位,都是從 000 0000~111 1111(byte為例)分布。如果前面符号位為1就是表示負數,負數的最小到最大(-128 ~ -1)共128個,如果是0就是正數的最小和最大(0 ~ 127)共128個。這樣了解是不是容易很多呢!

測試

上面講了那麼多道理,咱們測試一下吧,用以下代碼驗證上述結果

//微信公衆号:bigsai
public class Main {
    public static void main(String[] args) {
        int a =-1;//11111111 11111111 11111111 11111111
        int b=1;  //00000000 00000000 00000000 00000001
        System.out.println(Integer.toBinaryString(a));//輸出-1的二進制
        System.out.println(Integer.toBinaryString(b));//前面的0會省略
        
        /*
         * 127 + 1:
         *  0111 1111
         * +0000 0001
         * =1000 0000 = -128
         */
        byte c=127;
        byte d=(byte)(c+1);
        System.out.println(d);
        
        /*
         *  -1+1
         *   1111 1111
         * + 0000 0001
         * =10000 0000(理論上) = 0000 0000(隻有8位有效位) =0
         */
        byte e=-1;
        byte f=(byte)(e+1);
        System.out.println(f);
    }
}
           

輸出結果:

快速了解原碼、反碼、補碼

這段代碼意會鞏固以下就好了。

總結

到此,是不是對原碼、反碼、補碼了解更透徹一點了呢?不管你懂沒懂,反正問她懂了嗎她是這麼說的:

快速了解原碼、反碼、補碼

總結一下:

  • 原碼,能夠直接的顯示數值的大小狀況。結構為符号位+數值部分。符号位0代表正,1代表負。
  • 反碼,是一個過渡碼,其實就是在求補碼或者原碼補碼轉換過程中需要用到。其規則是正數反碼等于原碼,負數反碼符号位不變,數值位0變成1,1變成0.
  • 補碼,計算機中數值都是以補碼的形式進行計算的,它有效的解決負數加法問題,也可以使符号位直接參與運算。并且原碼、反碼、補碼轉換很簡單。

好了,本片已經結束了,講完自己的氣也消了一半,希望上面那個學弟能看到這篇文章然後點個贊。

如果感覺不錯,歡迎關注公衆号:

bigsai

一個和你一起成長的小哥哥。如果有錯誤或者不太好的地方,歡迎指正!