今天,發生一件很是有趣的事情。算法
公司同事問了我一個問題:爲何 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位。移位的規則是:
因左移就在右邊低位補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; } }
運行以後的結果,能夠在控制檯打印看到:
上邊第一個是咱們本身經過推算它的補碼,而後經過二進制轉十進制的一個算法算出來的最終結果,第二個就是直接經過位運算算出來的結果。能夠看到結果是如出一轍的。
至此,是否是對原碼,反碼,補碼以及位運算左移右移,有了比較清晰的認識了呢?