一. 什么是IEEE754標準
我們知道, 計算機內(nèi)部實際上只能存儲或識別二進制.
在計算機中, 我們?nèi)粘K褂玫奈臋n, 圖片, 數(shù)字等, 在儲存時, 實際上都要以二進制的形式存放在內(nèi)存或硬盤中, 內(nèi)存或硬盤就好像是一個被劃分為許多小格子的容器, 其中每個小格子都只能盛放0或1...
我們?nèi)粘J褂?浮點數(shù) 也不例外, 最終也要被存儲到這樣的二進制小格子中.
這就涉及到了 應該怎么存 的問題, 比如, 對于浮點數(shù) 20.5, 是應該存儲為 0100011 呢, 還是應該存儲為 1100110 呢?
事實上直到20世紀80年代, 還是計算機廠商各自為戰(zhàn), 每家都在設計自己的浮點數(shù)存儲規(guī)則, 彼此之間并不兼容. 直到1985年, IEEE754標準問世, 浮點數(shù)的存儲問題才有了一個通用的工業(yè)標準.
IEEE754標準提供了如何在計算機內(nèi)存中,以二進制的方式存儲十進制浮點數(shù)的具體標準,
IEEE754標準發(fā)布于1985年. 包括 javascript, Java, C在內(nèi)的許多編程語言在實現(xiàn)浮點數(shù)時, 都遵循IEEE754標準.
IEEE754的最新標準是IEEE754-2008, 但本篇文章主要參考的是IEEE754-1985, 好在兩者相差并不大, 而參照1985的標準可以讓我們對一些基礎概念有更好的理解
IEEE754提供了四種精度規(guī)范, 其中最常用的是 單精度浮點型 和 雙精度浮點型 , 但IEEE754并沒有規(guī)定32位浮點數(shù)類型需要叫做 float, 或64位浮點數(shù)需要叫做 double. 它只是提供了一些關于如何存儲不同精度浮點數(shù)的規(guī)范和標準. 不過一般情況下, 如果我們提到 float, 其實指的就是IEEE754標準中的32位單精度浮點數(shù). 如果我們提到 double, 其實指的就是IEEE754標準中的64位雙精度浮點數(shù)
下面是單精度浮點數(shù)和雙精度浮點數(shù)的一些信息, 可以先簡單看一下, 看不懂也沒關系, 下文會對這里的信息做詳細的解釋...
好啦, 鋪墊完了, 開始正文吧~
二. 32位單精度浮點數(shù)在內(nèi)存中的存儲方式
上文說到: IEEE754標準提供了如何在計算機內(nèi)存中, 以二進制的方式存儲十進制浮點數(shù)的具體標準, 并制定了四種精度規(guī)范.
這里我們主要研究 32位浮點數(shù) (或者說單精度浮點數(shù), 或者說float類型) 在計算機中是怎么存儲的. 其他精度, 比如64位浮點數(shù), 則大同小異.
想要存儲一個32位浮點數(shù), 比如20.5, 在內(nèi)存或硬盤中要占用32個二進制位 (或者說32個小格子, 32個比特位)
這32個二進制位被劃分為3部分, 用途各不相同:
這32個二進制位的內(nèi)存編號從高到低 (從31到0), 共包含如下幾個部分:
sign: 符號位, 即圖中藍色的方塊
biased exponent: 偏移后的指數(shù)位, 即圖中綠色的方塊
fraction: 尾數(shù)位, 即圖中紅色的方塊
下面會依次介紹這三個部分的概念, 用途.
1. 符號位: sign
以32位單精度浮點數(shù)為例, 以下不再贅述:
符號位: 占據(jù)最高位(第31位)這一位, 用于表示這個浮點數(shù)是正數(shù)還是負數(shù), 為0表示正數(shù), 為1表示負數(shù).
舉例: 對于十進制數(shù)20.5, 存儲在內(nèi)存中時, 符號位為0, 因為這是個正數(shù)
2. 偏移后的指數(shù)位: biased exponent
指數(shù)位占據(jù)第30位到第23位這8位. 用于表示以2位底的指數(shù). 至于這個指數(shù)的作用, 后文會詳細講解, 這里只需要知道: 8位二進制可以表示256種狀態(tài), IEEE754規(guī)定, 指數(shù)位用于表示[-127, 128]范圍內(nèi)的指數(shù).
不過為了表示起來更方便, 浮點型的指數(shù)位都有一個固定的偏移量(bias), 用于使 指數(shù) + 這個偏移量 = 一個非負整數(shù). 這樣指數(shù)位部分就不用為如何表示負數(shù)而擔心了. 規(guī)定: 在32位單精度類型中, 這個偏移量是127. 在64位雙精度類型中, 偏移量是1023.
所以這里的偏移量是127,
即, 如果你運算后得到的指數(shù)是 -127, 那么偏移后, 在指數(shù)位中需要表示為: -127 + 127(偏移量) = 0
如果你運算后得到的指數(shù)是 -10, 那么偏移后, 在指數(shù)位中需要表示為: -10 + 127(偏移量) = 117
看, 有了偏移量, 指數(shù)位中始終是一個非負整數(shù).
看到這里, 可能會覺得還不是很清楚指數(shù)的作用到的是什么. 沒關系, 讓我們先繼續(xù)往下看吧...
3. 尾數(shù)位:fraction
尾數(shù)位: 占據(jù)剩余的22位到0位這23位. 用于存儲尾數(shù).
在以二進制格式存儲十進制浮點數(shù)時, 首先需要把十進制浮點數(shù)表示為二進制格式, 還拿十進制數(shù)20.5舉例:
十進制浮點數(shù)20.5 = 二進制10100.1
然后需要把這個二進制數(shù)轉(zhuǎn)換為以2為底的指數(shù)形式:
二進制10100.1 = 1.01001 * 2^4
注意轉(zhuǎn)換時, 對于乘號左邊, 加粗的那個二進制數(shù)1.01001, 需要把小數(shù)點放在左起第一位和第二位之間. 且第一位需要是個非0數(shù). 這樣表示好之后, 其中的1.01001就是尾數(shù).
用 二進制數(shù) 表示 十進制浮點數(shù) 時, 表示為尾數(shù)*指數(shù)的形式, 并把尾數(shù)的小數(shù)點放在第一位和第二位之間, 然后保證第一位數(shù)非0, 這個處理過程叫做規(guī)范化(normalized)
我們再來看看規(guī)范化之后的這個數(shù): 1.01001 * 2^4
其中1.01001是尾數(shù),? 而4就是偏移前的指數(shù)(unbiased exponent), 上文講過, 32位單精度浮點數(shù)的偏移量(bias)為127, 所以這里加上偏移量之后, 得到的偏移后指數(shù)(biased exponent)就是 4 + 127 = 131, 131轉(zhuǎn)換為二進制就是1000 0011
現(xiàn)在還需要對尾數(shù)做一些特殊處理
1. 隱藏高位1.
你會發(fā)現(xiàn), 尾數(shù)部分的最高位始終為1. 比如這里的 1.01001, 這是因為前面說過, 規(guī)范化之后, 尾數(shù)中的小數(shù)點會位于左起第一位和第二位之間. 且第一位是個非0數(shù). 而二進制中, 每一位可取值只有0或1, 如果第一位非0, 則第一位只能為1. 所以在存儲尾數(shù)時, 可以省略前面的 1和小數(shù)點. 只記錄尾數(shù)中小數(shù)點之后的部分, 這樣就節(jié)約了一位內(nèi)存. 所以這里只需記錄剩余的尾數(shù)部分: 01001
所以, 以后再提到尾數(shù), 如無特殊說明, 指的其實是隱藏了整數(shù)部分1. 之后, 剩下的小數(shù)部分
2. 低位補0
有時候尾數(shù)會不夠填滿尾數(shù)位(即圖中的紅色格子). 比如這里的, 尾數(shù)01001不夠23位
此時, 需要在低位補零, 補齊23位.
之所以在低位補0, 是因為尾數(shù)中存儲的本質(zhì)上是二進制的小數(shù)部分, 所以如果想要在不影響原數(shù)值的情況下, 填滿23位, 就需要在低位補零.
比如,? 要把二進制數(shù)1.01在不改變原值的情況下填滿八位內(nèi)存, 寫出來就應該是: 1.010 0000, 即需要在低位補0
同理, 本例中因為尾數(shù)部分存儲的實際上是省略了整數(shù)部分 1. 之后, 剩余的小數(shù)部分, 所以這里補0時也需要在低位補0:
原尾數(shù)是:? ? 01001(不到23位)
補零之后是:? 0100 1000 0000 0000 000? (補至23位)
三. 實例: 表示十進制浮點數(shù)20.5
在上面的討論中, 我們已經(jīng)得出, 十進制浮點數(shù) 20.5 的:
符號位是: 0
偏移后指數(shù)位是: 1000 0011
補零后尾數(shù)位是: 0100 1000 0000 0000 000
現(xiàn)在, 把這三部分按順序放在32位浮點數(shù)容器中, 就是 0? ? 1000 0011? ? 0100 1000 0000 0000 000
這就在32位浮點數(shù)容器中, 以二進制表示了一個十進制數(shù)20.5的方式
這里有一個可以驗證的IEEE754浮點數(shù)內(nèi)存狀態(tài)的網(wǎng)站, 我們來驗證一下:
可見驗證是通過的. 不過為了加深理解, 我們再反向推導一遍:
假設現(xiàn)在我們有一個用二進制表示的32位浮點數(shù): 0? 1000 0011? 0100 1000 0000 0000 000, 求它所代表的十進制浮點數(shù)是多少?
觀察可知:
符號位是0: 所以這是個正數(shù).
尾數(shù)是: 0100 1000 0000 0000 000
去掉后面的補零, 再加上隱藏的整數(shù)部分1.? 得到完整的尾數(shù)(含隱藏的整數(shù)部分)為: 1.01001
偏移后的指數(shù)位為: 1000 0011, 轉(zhuǎn)換為十進制為131, 減去偏移量127, 得到真正的指數(shù)是 4
所以, 最后得到的浮點數(shù) = 尾數(shù)(含隱藏的整數(shù)部分) * 以2為底的指數(shù)次冪
=? 二進制的: 1.01001 * 2^4
=? 把小數(shù)點向右移動4位
=? 二進制的10100.1
=? 十進制位20.5
注意, 直到最后一步才把二進制轉(zhuǎn)換為十進制.
附帶的, 這里還有一個進制轉(zhuǎn)換網(wǎng)站, 可以看到二進制的10100.1, 確實等于十進制的20.5
到這里就講解的差不多了,
隨后是一張大體的計算方法示意圖
還有雙精度類型的內(nèi)存狀態(tài)示意圖:
下一篇會講述為什么32位單精度浮點數(shù)的取值范圍是, 這個值究竟是如何計算出來的...
下一篇再見吧~
注:
這一系列文章其實是我在學習IEEE754標準的過程中, 總結(jié)的一系列筆記. 其中包含了一些個人理解, 所以如有偏差, 還望指出.
整個系列大概會包含如下五篇文章:
一. 浮點數(shù)在內(nèi)存中的存儲方式
二. 浮點數(shù)的取值范圍是如何計算的
三. 浮點數(shù)的精度是如何計算的
四. 非規(guī)格數(shù), ±0, ±infinity和NaN都是什么
五. 浮點數(shù)的舍入規(guī)則(rounding)
下一篇再見~