終於熬過了業務最繁忙的開學季,收拾收拾心情,從新撿起來一些基礎的知識,願各位都能堅持下去~加油!html
以前簡單介紹了二進制下整數的加減乘除基本運算,建議沒看過的小夥伴先簡單看一下,這一節,一塊兒探索一下浮點數的在二進制中是怎麼進行基本運算的,同時一塊兒瞭解一下精度丟失的問題~python
打開chrome控制檯,給一個特別簡單的輸入以下:算法
0.1 + 0.2 // 0.30000000000000004
複製代碼
不知道你有沒有吃驚,這麼簡單的一個計算,不管在js中仍是在python中,都不是準確的0.3,這是爲何呢?chrome
要了解這個問題,首先咱們須要知道浮點數在計算機中究竟是如何進行存儲的?不知道你是怎麼想的,總之我開始的第一反應就是假設是32位的存儲空間,我可能會按照整數的存儲方式去想象,好比1-24位是整數位,剩餘的8位表明小數,這樣能夠嗎?固然是能夠的,可是先考慮下下面的這個問題:數據庫
想象紅色區域是所能放置的數字的最大空間,如今有個問題,當咱們想繼續加0的時候,發現放不下了,由於空間是有限的,這個時候,咱們會怎麼辦?post
對,沒錯,科學計數法,就是咱們在學習過程當中,若是位數太多,咱們通常都會用科學計數法來表示,這樣的好處是,書寫的位數小,表示的位數多,因此,回到計算機中,32位來表示實數的話,最多能表示多少位?2^32次方個,大約就是40億,40億數字不少嗎?多,可是和無限多的實數集來比,滄海一粟,不夠看的,因此計算機的設計者就要考慮這個問題了,如何讓計算放下更多的數字?學習
還記得上面說的,1-24表示整數位,剩餘的表示小數位嗎?這種存儲方式就叫定點數,1-24位每4位表示一個0~9的數字的話,能夠有6位表示整數部分,剩餘2位表示小數部分,這樣咱們能夠用32位表示從0到999999.99這樣1億個實數,這種用2進制來表示10進制的方式,叫作BCD編碼(Binary-Coded Decimal),好比說8421碼,從左往右的權依次是8,4,2,1,等等,有興趣的能夠去了解一下。ui
定點數有幾個明顯的缺點:編碼
其實究其根本緣由,仍是這種方式的「有限」限制了它,那麼有沒有一種方式,可讓32位所能表示的數字,更「無限」一點,更適合咱們的訴求?spa
固然,設計計算機的前輩智慧是無限的~
就像使用科學計數法同樣,計算機前輩在浮點數的設計中也用了同樣的思想,IEEE的標準定義了2個基本的浮點數格式,一個是32位的單精度浮點數,一個是64位的雙精度浮點數,也就是float或float32和double或float64這兩個數據格式,雙精度和單精度的表示形式是差很少的,咱們以單精度的做爲了解和學習。
分爲3部分:
綜合上述表示和科學計數法,咱們的浮點數就能夠表示爲公式
(-1)^s * 1.f * 2^e
看完公式有沒有發現問題?你會發現,咱們這個公式沒法表示0,的確,這是一個巧妙的設計,咱們用0(8個bit都爲0)和255(8個bit都爲1)來表示一些特殊的數值,能夠認爲他們2個是特殊的flag位,好比當e和f都爲0的時候,咱們就認爲這個浮點數是0,看下錶:
以0.5爲例,0.5的符號位s是0,f也是0,e是-1,
這樣(-1)^0 * 1.0 * 2 ^ -1 = 0.5
用32位bit表示就是
s | e | f | |
---|---|---|---|
0 | 0111 1110 | 0000 ...0 | |
1位 | 8位 | 23位 | 0.5 |
經過這樣的表示方式,能夠明顯的發現32位所能表示的實數範圍是很大的,又由於這種方式建立的實數中小數點的位置是能夠」浮動「的,因此也被叫作浮點數,
到這裏咱們知道了浮點數是怎麼存儲的了,可是還沒解決咱們開始的問題,爲什麼0.1+0.2!=0.3,首先咱們要知道0.1是怎麼存儲的:
(-1)^s * 1.f * 2^e = 0.1
求解e
s=0 f=0 e=Math.log2(0.1) // -3.321928094887362
能夠看出來這裏0.1是算不出來一個準確數字的,從0.1到0.9只有0.5是能夠求出一個準確的值的,剩下的都算不出來一個準確的值,這也就是爲何0.1+0.2會致使的精度問題,也就是說浮點數不管是表示仍是計算其實都是近似計算,而近似計算就必定會致使一些問題,好比,你但願銀行給你存錢以及算利息的時候用浮點數計算嗎?固然不但願,不然你的錢算多了還好,算少了豈不是虧大了~
把一個二進制表示的浮點數(0.1001),轉爲10進製表示,由於小數點後的每一位都表示的是2的-N次方,所以轉爲10進制就是:
(1 * 2 ^ -1) + (0 * 2 ^ -2) + (0 * 2 ^ -3) + (1 * 2 ^ -4) = 0.5625
能夠理解爲,對於二進制轉十進制來講,從小數點開始,往左就是把2的指數從0開始過一位+1,包括0,往右就是從-1開始依次-1。
把一個10進制的浮點數,轉爲二進制的話,和整數的二進制表示採用「除以 2,而後看餘數」的方式相比,小數部分轉換是用一個類似的反方向操做,就是乘以2,而後看是否大於1,若是大於1就記下1並把結果減去1,一直重複操做。
好比,十進制的9.1,小數部分0.1轉爲2進制的過程爲:
這是獲得一個無限循環的部分」0011「,整數部分9轉爲二進制就是1001,所以結果就是1001.000110011...
把小數點作移3位,獲得一個浮點數的結果是 1.001000110011... * 2 ^ 3
找到咱們上面的公式 (-1)^s * 1.f * 2^e 套公式可獲得:
s = 0 f = 00100011001100110011 001(到23位後自動捨棄,由於最長只能放23位有效數字)
指數位是3,咱們e的範圍是1-254 對半分正數和負數,因此127表示0,從127開始加3,獲得結果是130,130轉爲二進制表示結果就是: 1000 0010, 因此獲得e=1000 0010, 結果以下:
因此最終的二進制表示結果是: 0100 0001 0001 0001 1001 1001 1001 1001
若是咱們再把這個浮點數表示換算成十進制, 實際獲得的準確值是 9.09999942779541015625。相信你如今應該不會感受奇怪了。
首先,咱們瞭解一下浮點數的加法計算過程是怎麼樣的,拿0.5 + 0.125來作計算,首先0.5套用公式計算結果是:
s = 0 有效位1.f = 1.0000... e = -1;
0.125 轉換爲:
s = 0 有效位1.f = 1.0000... e = -3;
而後,計算口訣是 指數位先對齊(小轉大,這裏要把e統一爲-1), 而後按位相加符號位和有效位,e保持統一後的結果,所以:
符號位s | 指數位e | 有效位1.f | |
---|---|---|---|
0.5 | 0 | -1 | 1.0 |
0.125 | 0 | -3 | 1.0 |
0.125對齊指數位 | 0 | -1 | 0.01 |
0.5 + 0.125 | 0 | -1 | 1.01 |
結果就是 (-1)^0 * 1.25 * 2^-1 = 0.625;
ps: 爲啥是1.25?雖然咱們計算得出的是1.01 可是不要忘記計算是經過2進制算的,計算十進制的時候要轉回來哦,因此0100000.... 後面都是0不用管,小數部分,從頭開始乘以2的-N次別忘了,因此結果就是2^-2 = 0.25 加上整數位的1 就是1.25了~
能夠發現,其實浮點數的計算過程,經過一個加法器也是能夠實現的,電路成本一樣不會很高,可是須要注意一些別的問題:
計算過程當中,須要先對齊,可是有效數位的長度是23位,假若有一個很大的數字和一個很小的數字進行相加,而後對齊的過程當中,小數被0部位過程當中直接溢出了,23位不夠用了,就會出現問題,補完後一些有效位被丟掉了,從而致使結果上的偏差,兩個數的指數位差超過23,好比到2^24位(差很少1600萬倍),這2個數相加後,結果就直接是較大數,較小數徹底被拋棄了。。。
有些同窗會急急忙忙去chrome的控制輸入下面的代碼:
Math.pow(2, 24) + 0.1 // 16777216.1
複製代碼
騙人,結果不是還有0.1嗎,別急,小夥伴,js內置的Number是64位的,你能夠試試
Math.pow(2, 50) + 0.1 // 1125899906842624
複製代碼
是否是小數沒了?【這種現象也叫大數吃小數】
因此若是銀行採用IEEE-754 32位的浮點數計數方法來保管存款的話,假設你是一個大老闆,你的帳戶中有2000萬rmb,這個時候你的某一個員工給你打了1塊錢,哈哈對不起,銀行給算丟了,你的存款是不變的!因此,通常銀行啊,電商一類的都會在涉及到錢的時候使用定點數或者整數來計算,避免出現精度丟失的問題,若是你去銀行涉及數據庫,必定要當心謹慎~
這篇文章咱們從浮點數的表示開始,到存儲,到轉換以及計算過程分析了真實的計算機世界中浮點數究竟是怎麼運行的,從中也瞭解了浮點數究竟爲什麼會丟失精度:
精度丟失不是無法解決的,有成熟的方案,不作過多介紹,有興趣你們能夠去研究:
文章內容大部分參考自 徐文浩 老師的 「深刻淺出計算機組成原理」專欄,加了一些本身的理解作了一個簡單的總結,以後還會繼續不定時的分享一些本身的所得,若是以爲還不錯,點個贊吧~
ps: 有同窗可能會問,既然只有0.5能夠轉爲一個準確的數字,爲什麼0.1+0.1沒有問題,這個我還沒仔細研究,不過我猜測是由於自己計算就是一個計算近似值的過程,所以再得出結果後,若是還在一個近似範圍內,就會認爲沒有偏差,超過這個範圍,則會認爲出現偏差了,總之咱們能夠確認的是計算過程當中拿到的確實是一個近似數了,這個也確實是致使一些浮點數計算丟失精度的緣由~
有興趣的話能夠到這裏查看實際的數字在計算機中存儲的具體內容~