二進制基礎:補碼,左移,右移

引入

二進制是計算機的基礎,追根溯源還是因爲Si的半導體性。

除了二進制,還有十六進制,它是簡化二進制的表示。

做個測試:

  @Test
    public void testHex() {
        int n = 0x77d45d25;
        System.out.println(
                Integer.toBinaryString(n));
    }

0x表示這是一個十六進制數。

結果:

1110111110101000101110100100101

我們自己轉換一下:

在這裏插入圖片描述

控制檯輸出的結果它把最高位的0給舍掉了,但這個不影響。

爲什麼要有補碼

先看0000到1111的二進制數:

十進制代表的就是0到15

那這裏爲什麼要畫成一個圈呢?因爲有溢出的概念。

我們等下會測試。

現在的問題是:怎麼纔能有負數?

解決的辦法就是,把0到15這16個數分一半給負數:

這樣就ok了。我們終於有負數了。

這麼一劃分出現了一個神奇的事情:

這個圈圈上的任意一個數,它的二進制取反之後,再加一(順時針移動一次),會變成原數的相反數!

比如2,它的二進制是0010

取反:1101

加一:1110

在圖上看看,1110不就是-2嗎?


拿代碼測一下:

 @Test
    public void testInverse(){
        System.out.println(~100+1);
    }

結果:

-100

特殊的值

我認爲的補碼就是因爲負數而產生的。

我們觀察上面的圖,注意幾個特別的數:

在四位的情況下:

0—>0000
-1—>1111
最小值 —> 1000
最大值 —> 0111

你需要記住的是,任何位數,-1永遠都是一羣1。


    @Test
    public void testMore() {
        int n = -9;
        System.out.println("-9的二進制:" + Integer.toBinaryString(n));
        n = -1;
        System.out.println("-1的二進制:" + Integer.toBinaryString(n));


        int max = Integer.MAX_VALUE;
        int min = Integer.MIN_VALUE;
        System.out.println("最大的整數: " + max);
        System.out.println("最小的整數:" + min);

        System.out.println("最大整數的二進制: " +
                Integer.toBinaryString(max));
        System.out.println("最小整數的二進制: " +
                Integer.toBinaryString(min));

   
    }

結果:

-9的二進制:11111111111111111111111111110111
-1的二進制:11111111111111111111111111111111
最大的整數: 2147483647
最小的整數:-2147483648
最大整數的二進制: 1111111111111111111111111111111
最小整數的二進制: 10000000000000000000000000000000

這裏是32位,和我們探究的4位理論上是一樣的。

那麼,如何才能快速知道-9的二進制表示呢?

首先,你知道-1是32個1(11111111111111111111111111111111)

然後,因爲-9是-1減掉8,所以你用32個1去減8(1000)

另外一個有趣的地方就是:

最大值加一變成最小值。

這個是正常現象,不能叫做溢出。

我們拿8位的byte做個例子:

    @Test
    public void testbyte() {
        byte b = 127;
        byte ans = (byte) (b + 1);
        System.out.println(ans);
    }

結果:

-128

溢出

還是看4位的情況。

1111如果還要加1,就會溢出。

溢出自動捨去高位:

於是結果就是0000

溢出保證我們的整個邏輯還是正確的。

數學移位

什麼是數學移位?

就是>>以及<<

它是二進制數的移動,所以計算起來非常之快。

當然,你完全可以將其理解爲乘2和除2。

>>是右移,右移它的數量級就會降低,移一次就會有除以2的效果。

相反,<<是左移,左移一次就會有乘以2的效果。

看個測試:

  @Test
    public void testMoveBit(){
        System.out.println(50>>1);
        System.out.println(50<<1);
        System.out.println(50>>2);
        System.out.println(50<<2);
        System.out.println("------------------------");
        System.out.println(-50>>1);
        System.out.println(-50<<1);
        System.out.println(-50>>2);
        System.out.println(-50<<2);
    }

結果:

25
100
12
200
------------------------
-25
-100
-13
-200

唯一要注意的就是除不盡的時候要取整。

還有一個很經典的問題,就是右移的時候,高位到底是補0還是補1。

用代碼看一下就知道了:

  @Test
    public void testMoveBitPositiveAndNegative(){
        System.out.println("positive--------------------");
        System.out.println(Integer.toBinaryString(50));
        System.out.println(Integer.toBinaryString((50>>2)));
        System.out.println("negative--------------------");
        System.out.println(Integer.toBinaryString((-50)));
        System.out.println(Integer.toBinaryString((-50>>2)));
    }

結果:

positive--------------------
110010
1100
negative--------------------
11111111111111111111111111001110
11111111111111111111111111110011

正50它把前面的0全部給省略了,我們自己補上:

正數數學右移的時候,就是高位補0,低位溢出。

負數數學右移的時候,從控制檯的結果可以看到,是高位補1,低位溢出。

邏輯位移

邏輯位移只有右移,沒有左移,右移的符號是>>>

它的作用很大,源碼中經常出現邏輯右移,主要作用就是用來將數字拆分成一個一個字節的。

    @Test
    public void testMoveBitLogic(){
        System.out.println(50>>>1);
        System.out.println(50>>>2);
        System.out.println("------------------------");
        System.out.println(-50>>>1);
        System.out.println(-50>>>2);
    }

結果:


25
12
------------------------
2147483623
1073741811

正數的行爲和數學右移很像。但是,它不是用來做數學計算的。所以如果你想要快速地完成除以2的工作,請用>>

負數的右移很奇怪,那就是高位補0還是補1的問題了。

測試:

    @Test
    public void testMoveBitLogicBinary(){
        System.out.println("positive--------------------");
        System.out.println(Integer.toBinaryString(50));
        System.out.println(Integer.toBinaryString((50>>>2)));
        System.out.println("negative--------------------");
        System.out.println(Integer.toBinaryString((-50)));
        System.out.println(Integer.toBinaryString((-50>>>2)));
    }

結果:

positive--------------------
110010
1100
negative--------------------
11111111111111111111111111001110
111111111111111111111111110011

正數的時候和數學右移一樣,都是高位補0。

負數的時候,我們睜大眼睛看看:


負數邏輯右移,是高位補0,低位溢出。

邏輯右移的應用

看一下RandomAccessFilewriteInt方法:

 public final void writeInt(int v) throws IOException {
        write((v >>> 24) & 0xFF);
        write((v >>> 16) & 0xFF);
        write((v >>>  8) & 0xFF);
        write((v >>>  0) & 0xFF);
        //written += 4;
    }

可以看出來,它是一個一個字節寫出去的,從高八位開始。

其實在網絡傳輸中,傳輸的都是字節,所以也會用到邏輯右移。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章