負數在計算機中是怎麼存儲

今天,發生一件很是有趣的事情。算法

公司同事問了我一個問題:爲何 2.0 - 1.1 = 0.89999999 呢?不該該是 0.9嗎?學習

原來是,他問了周圍一圈的同事,都給他的是同一個回答,說這是精度問題。他百思不得其解,怎麼就會產生精度問題呢。再問,就沒人知道緣由了。3d

而後,我就看到了他抱着一本厚厚的書在看。拿過來一看,是一本Java書,厚厚的六百多頁,這還僅是第一卷。喲呵,這是準備大幹一場啊。code

看在他這麼努力學習的份上,還有他那對知識極度渴望的眼神。我決定,把我畢生所學傳授與他。blog

因而,就給他詳細講解了,計算機中是怎麼存儲一個數的,十進制是怎麼在轉二進制的過程當中丟失精度的,以及浮點數是怎麼遵循IEEE 754 規範的,在浮點數進行加減運算的過程當中會經歷對階、移位運算等過程,以及在此過程當中是怎麼丟失精度的。(這些問題在以前的文章中都有解答,參看「爲何0.1+0.2=0.30000000000000004」)class

而後,成功的把他完全搞懵逼了。怎麼這麼難啊。test

原來,他的計算機基礎比我還匱乏,不知道什麼是位運算,不知道什麼是原碼、反碼和補碼。基礎

本着個人熱心腸,我就給他普及了一下這些知識 ---- 負數的補碼形式和位移運算。file

咱們知道,一個數分爲有符號和無符號。對於,有符號的數來講,最高位表明符號位,即最高位1表明負數,0表明正數。二進制

在計算機中,存儲一個數的時候,都是以補碼的形式存儲的。而正數和負數的補碼錶示方式是不同的。正數的補碼就等於它的原碼,而負數的補碼是原碼除符號位之外都取反,而後 + 1 得來的。以一個int類型爲例(4個字節即32位)

14的原碼爲:

0000 0000 0000 0000 0000 0000 0000 1110

它的反碼、補碼和原碼都是同樣的。

-14的原碼爲:

//最高位1爲符號位,表明此數爲負數
1000 0000 0000 0000 0000 0000 0000 1110

反碼爲原碼除了符號位之外的其餘位都取反(即0變爲1,1變爲0),

1111 1111 1111 1111 1111 1111 1111 0001

補碼爲反碼 + 1 ,注意二進制中是滿二進一。

1111 1111 1111 1111 1111 1111 1111 0010

位的左移,右移運算就是分別向左和向右移動N位。移位的規則是:

  1. 無論有沒有符號位,左移都是在低位補0
  2. 帶符號右移,是在高位補符號位,即正數補0,負數補1
  3. 無符號右移,不管該數是正數仍是負數都在高位補0

因左移就在右邊低位補0就能夠了,比較簡單,我就以負數的右移來舉例,是怎麼計算無符號右移和帶符號右移的。仍是以 -14 爲例。

// -14的補碼
1111 1111 1111 1111 1111 1111 1111 0010
// 帶符號右移用 >> 表示,即右移一位 -14>>1,高位補符號位1,低位捨去
1111 1111 1111 1111 1111 1111 1111 1001
// 無符號右移用 >>> 表示,即右移一位 -14>>>1,最高位補0
0111 1111 1111 1111 1111 1111 1111 1001

咱們能夠經過程序來驗證一下 -14>>1和 -14>>>1的結果是否正確。

1. -14>>1 = -7

//咱們算出來 -14>>1的補碼爲:
1111 1111 1111 1111 1111 1111 1111 1001
//那它具體表明的數值是多少呢?
//首先,補碼 -1 獲得反碼
1111 1111 1111 1111 1111 1111 1111 1000
//而後,反碼取反獲得原碼,最高位符號位不變
1000 0000 0000 0000 0000 0000 0000 0111

這結果不就是 -7 嗎,而後經過程序計算一下結果:

public class TestMove {
    public static void main(String[] args) {
        System.out.println( -14>>1);
    }
}

結果一樣也是-7 。說明了咱們位移操做沒問題。

2. -14>>>1=2147483641

咱們經過一段程序去驗證:

package com.test.binary;
​
/**
 * @Author zwb
 * @DATE 2019/12/3 15:49
 */
public class TestBinary {
    public static void main(String[] args) {
        //咱們本身計算出來的 -14>>>1 結果
        String bin = "01111111111111111111111111111001";
        double res = binToDec(bin);
        System.out.println(res);
        //經過計算機計算的結果
        System.out.println(-14>>>1);
    }
​
    //二進制轉爲十進制
    public static double binToDec(String bin){
        int index = bin.indexOf(".");
        int len = bin.length();
        double res = 0;
        //index爲-1說明沒有小數
        if(index == -1){
            for(int i = 0; i< len; i++){
                res += Math.pow(2,i) * Integer.parseInt(String.valueOf(bin.charAt(len-1-i)));
            }
        }else{
            //整數部分
            int partA = 0;
            for(int i = 0; i< index; i++){
                partA += Math.pow(2,i) * Integer.parseInt(String.valueOf(bin.charAt(index-1-i)));
            }
            //小數部分
            double partB = 0;
            for(int j = index + 1; j < len; j++){
                partB += Math.pow(2,index - j) * Integer.parseInt(String.valueOf(bin.charAt(j)));
            }
            res = partA + partB;
        }
        return res;
    }
}

運行以後的結果,能夠在控制檯打印看到:
file

上邊第一個是咱們本身經過推算它的補碼,而後經過二進制轉十進制的一個算法算出來的最終結果,第二個就是直接經過位運算算出來的結果。能夠看到結果是如出一轍的。

至此,是否是對原碼,反碼,補碼以及位運算左移右移,有了比較清晰的認識了呢?

相關文章
相關標籤/搜索