天天看點

【從入門到放棄-Java】碼出高效-計算機基礎-二進制和浮點數前言二進制浮點數總結

前言

最近在學習孤盡大神内網分享的資料時,被安利了一本書【碼出高效】,這也是他在【阿裡巴巴Java開發手冊】一書後,結合阿裡的生産經驗和曆史故障給出的最佳研發實踐。

剛拿到書,打開目錄,會覺得這是一本基礎入門書,裡面的東西大家都懂,但是沒翻幾頁你就會被裡面生動的案例和技術細節吸引,有很多在日常開發中會經常用到,但一直被忽略的點,這些點往往是引發故障的點。現在就将我在學習過程中的筆記和收獲記錄下來和大家一起分享。

本篇就從計算機入門的二進制和浮點數開始。

二進制

起源

簡單來說,計算機是由半導體和電路闆組合起來的電子裝置,資訊存儲和邏輯計算的中繼資料歸根結底都是0和1的信号處理。

隻有0和1,進位規則是逢二進一,借位規則是借一當二,這就是二進制。

編碼方式

通過符号位和數字實際值可以表示數,有以下三種基本編碼方式

原碼

正數部分是數值本身,符号位為0;負數數值部分是數值本身,符合位為1。八位二進制數表示範圍是[-127, 127]。這是最符合人類認知的編碼方式。

反碼

正數部分是數值本身,符号位為0;負數數值部分是正數的基礎上對各位取反,符号位為1。八位二進制的表示範圍是[-127, 127]。

補碼

正數部分是數值本身,符号位為0;負數樹值部分是正數的基礎上對各位取反後加一,符号位為1.八位二進制的表示範圍是[-128, 127]。

加減運算

因為計算機中隻有加法器,沒有減法器。通過原碼相加,結果會出錯。如:

1 - 2 = 1 + ( -2 ) = -1

通過原碼計算為[0000 0001] + [1000 0010] = [1000 0011] = -3,結果明顯是不對的。

通過反碼計算為[0000 0001] + [1111 1101] = [1111 1110] = -1,結果正确。

但是反碼在某些情況出現新的問題。如:

2 - 2 = 2 + ( -2 ) = 0

通過原碼計算為[0000 0010] + [1000 0010] = [1000 0100] = -4,結果不正确。

通過反碼計算為[0000 0010] + [1111 1101] = [1111 1111] = -0,反碼有+0和-0的區分,用反碼計算也是有歧義的。

通過補碼計算為[0000 0010] + [1111 1110] = [0000 0000] = 0,補碼中隻有0,結果符合預期。

加減法是一個高頻運算,使用同一個運算器,可以減少中間變量的存儲及轉換成本,也降低了CPU設計的複雜度。

位運算

左移:<<,相當于乘2

右移:>>,相當于除2(最後一位是奇數時,存在誤差),帶符号位右移動,負數最高位補1,正數最高位補0。是以可以通過使用 ((b >> 31) ^ (a >> 31)) == 0 來判斷兩個整數正負是否相同。

無符号右移:>>>,無符号位右移,正數和負數最高位都補0,主要用于一些資料轉換,如加密、壓縮、影音編碼等場景。

取反:~,按位取反,0取反是1

與:&,按位與,相同位都為1才,就為1

或:|,按位或,相同位隻要有一個是1,就為1

異或:^,按位異或,相同位相等的時候為1,不相同的時候為0

浮點數

浮點數采用科學技術法來表示的,由符号位、有效數字、指數三部分組成。但編碼過程中經常會出現丢失精度的問題,如下:

float a = 1f;
float b = 0.9f;
float c = (a - b);
//c = 0.100000024
System.out.println(c);           

常見的浮點數有單精度和雙精度浮點數,占用位元組數和取值範圍不同。單精度四個位元組,雙精度八個位元組。

單精度浮點數的構成是1位符号位,8位階碼位(指數),23位尾數位(有效數字)。

階碼使用移碼來表示,尾數使用原碼來表示。移碼範圍是[0, 255]去掉特殊的0(計算機認為全零是機器0)和255(計算機認為是無窮大),階碼的取值範圍是[1, 254],根據移碼的定義,[x]=x+2^(n-1),n是8,是以x的取值範圍是[-126, 127]

尾數位是原碼表示的,1.111....111(23個1), 1 <= a < 2, 是以規格化後的尾數首個1會被省略,是以實際能表示24位尾數,最大值無限接近于2,是以單精度浮點數能表示的最大值為2^127,約等于(1.7*10^38)。

小數加減需要将小數點對齊後進行同位相加減,是以浮點數加減需要先将指數對齊,再進行同位加減

零值檢測

參與運算的兩個數中,隻要有一個是0就直接得出結果。因為浮點數運算過程比較複雜

對階操作

小數點需要對齊,當階碼大小不相等時,需要先對階,通過移動尾數改變階碼大小,尾數向右移一位,階碼值加一,反之減一。移動尾數過程中可能存在部分二進制被移出。但如果向左移動,會使高位移出,誤差更大,是以規定在對階時,選擇階碼小的數進行操作

尾數求和

對接完成後,按位相加即可,如9.8*10^38 + 6.5*10^37 = 9.8*10^38 + 0.65*10^38 = 10.45^38

結果格式化

求和完畢後,如果整數為不在[1, 9],則需要左右調整,尾數向右移動稱為右規,向左移動稱為左規。10.45^38要調整為1.045^39

結果舍入

因為對接或右規時,尾數需要移動,移出的位會被丢棄,為了減少精度損失,需要先将移出的這部分資料儲存出來,稱為保護位,格式化後再根據保護位進行舍入處理。

示例

1.0 - 0.9

1.0 - 0.9 = 1.0 + (-0.9)
1.0的浮點數辨別           

二進制表示

1.0:  [0011 1111 1000 0000 0000 0000 0000 0000]
-0.9: [1011 1111 0110 0110 0110 0110 0110 0110]           

對階

1.0的階碼是127,-0.9的階碼是126,比較階碼後需要向右移動-0.9位數的補碼,使其變成127,最高位補1。

-0.9的尾數位補碼為:[0001 1001 1001 1001 1001 1010]
對階後為:[1000 1100 1100 1100 1100 1101]           

尾數轉換成補碼相加

[1000 1100 1100 1100 1100 1101]
+[1000 0000 0000 0000 0000 0000]
=[0000 1100 1100 1100 1100 1101]           

規格化

尾數的最高位必須是1,是以需要将結果向左移動4位,階碼減4。

符号位為1

移動後階碼等于123(二進制[1111011])

尾數為[1100 1100 1100 1100 1101 0000]

隐藏最高位後是[100 1100 1100 1100 1101 0000]

最終1.0-0.9二進制表示[1011 1101 1100 1100 1100 1100 1101 0000]

尾數小數點後對應的十進制是 (1 + 2^-1 + 2^-4 + 2^-5 + 2^-8 + 2^-9 + 2^-12 + 2^-13 + 2^-16 + 2^-17 + 2^-19) * 2^-4 = 0.100000024           

是以通過上面的執行個體分析,我們了解了 為什麼浮點數1.0-0.9 結果為 0.100000024

總結

二進制和浮點數是我們最初接觸計算機時學習的基礎理論,日常開發中也會經常遇到,但很容易忽視。下面總結下我學完本章内容收獲到有意思的知識點:

  • 反碼和補碼誕生的原因:計算機中隻有加法器,沒有減法器。通過原碼相加,結果會出錯,是以出現了反碼,又因為反碼存在+0和-0問題,是以出現了補碼。
  • 判斷兩個數符号是否相同:可以通過使用 ((b >> 31) ^ (a >> 31)) == 0 來判斷兩個整數正負是否相同
  • 浮點數分單精度和雙精度:單精度浮點數占四個位元組32位,從左到右,1位符号位,8位階碼位(指數的移碼),23位尾數位
  • 浮點數加減運算:0值檢測 -> 對階 -> 尾數求和 -> 規格化 -> 結果舍入

更多文章

見我的部落格:

https://nc2era.com

written by

AloofJr

,轉載請注明出處