第一章提到過, ieee754標準中, 浮點數(shù)包含三種狀態(tài)
1. normal number(規(guī)格數(shù))?
2. subnormal number(非規(guī)格數(shù))
3. non-number(特殊數(shù))
本章詳細講解這三種狀態(tài).
一. 首先, 如何區(qū)分這三種狀態(tài)
其實這三種狀態(tài)是通過指數(shù)部分區(qū)分的, 而且很容易區(qū)分.
以32位浮點數(shù)為例, 其內存狀態(tài)分為3部分:
1位符號位? ? ?8位指數(shù)位? ? ?23位尾數(shù)位
其中, 如果8位指數(shù)位全為0, 就代表當前數(shù)是個非規(guī)格數(shù). 或者說, 形如 * 00000000 *********************** 格式的數(shù)就是非規(guī)格數(shù).
如果8位指數(shù)位全為1, 就代表當前數(shù)是個特殊數(shù). 或者說,?形如 *?11111111?*********************** 格式的數(shù)就是特殊數(shù).
如果8位指數(shù)不全為0, 也不全為1(也就是除去以上兩種狀態(tài)外, 剩下的所有狀態(tài)), 這個數(shù)就是規(guī)格數(shù).
隨便幾個例子: * 10101100 ***********************就是一個規(guī)格數(shù)
可見: 非規(guī)格數(shù)和特殊數(shù)是兩種特殊狀態(tài), 規(guī)格數(shù)則是非常常見的狀態(tài)
示意圖:
注意下圖把特殊數(shù)分為了兩種狀態(tài), 無窮大和NaN:
二. 這三種狀態(tài)的作用
為什么要把浮點數(shù)分為這三種狀態(tài)呢? 答案當然是有用啊, 而且作用相當直觀:
規(guī)格數(shù): 用于表示最常見的數(shù)值, 比如1.2, 34567, 7.996, 0.2. 但規(guī)格數(shù)不能表示0和非橙靠近0的數(shù).
非規(guī)格數(shù): 用于表示0, 以及非郴沸危靠近0的數(shù), 比如1E-38.
特殊數(shù): 用于表示"無窮"和"NaN":
浮點數(shù)的存儲和計算中會涉及到"無窮"這個概念, 比如:
32位浮點數(shù)的取值范圍是, 如果你要往里面存儲4e38(這超過了最大的可取值), 32位浮點數(shù)就會在內存中這樣記錄 "你存儲的數(shù)超過了我的最大表示范圍, 那我就記錄你存儲了一個無窮大..."
浮點數(shù)的存儲和計算中還會涉及到"NaN (not a number)"這個概念, 比如:
你要給一個負數(shù)開根號(如 √-1), 但是ieee754標準中的浮點數(shù)卻不知道該怎么進行這個運算, 它就會在內存中這樣記錄 "不知道怎么算, 這不是個數(shù)值, 記錄為NaN"
可見, 這三種狀態(tài)都是非常有用的, 作用也非常直觀, 下面我們一個個來講.
三. 狀態(tài)1: 規(guī)格數(shù)
對于規(guī)格數(shù):?
符號位, 1位: 可正可負
指數(shù)位, 8位: 不全為0, 且不全為1
對于32位浮點數(shù)來說, 規(guī)格數(shù)的指數(shù)位的取值范圍是[1, 254], 偏置bias是127, 所以實際的指數(shù)是:
[1 - 127, 254 - 127], 即 [-126, 127]
注: 關于偏置, 可參見本系列第一章, 此處不再贅述
尾數(shù)位, 23位: 尾數(shù)位前隱藏的整數(shù)部分是1.? 而非 0.
所以尾數(shù)位的取值范圍是[1.00000000000000000000000, 1.11111111111111111111111] (二進制)
換算為10進制為[1,2)
注: 關于尾數(shù)位前隱藏的數(shù),?可參見本系列第一章, 此處不再贅述
規(guī)格數(shù)的局限性: 無法表示 0 和 及其靠近0 的數(shù)
原因很簡單, ieee754浮點數(shù)的求值公式是:
所以可求出32位浮點數(shù)的取值范圍就是:
問題就出現(xiàn)在這里:?
注意尾數(shù)部分: 取值范圍是[1, 2), 始終大于1
注意指數(shù)部分:, 這個數(shù)始終大于0, 即便2^-167非常小, 但還是大于0
那么: 一個始終大于1的數(shù) * 一個始終大于0的數(shù), 永遠無法等于0
事實上, 1(尾數(shù)最小值) *?2^-167(指數(shù)最小值) =?2^-167.? ? 2^-167就是當前我們能表示的最小值
也就是說: 使用規(guī)格數(shù)時, 我們除了無法表示0, 也無法表示(0, 2^-167)之間的, 靠近0的極小數(shù)...
這就是規(guī)格數(shù)的局限性, 這個局限性將由非規(guī)格數(shù)解決.
補充一點:
其實在本系列的第二章, 我們計算過32位浮點數(shù)的取值范圍:
所以這里可以畫一個示意圖:
↑ 綠色區(qū)域就是32位浮點數(shù)中規(guī)格數(shù)的取值范圍, 可見它取不到0和靠近0的極小數(shù)
↑ 紅色區(qū)域包含0和靠近0的極小數(shù), 紅色區(qū)域其實是非規(guī)格數(shù)的取值范圍, 見下一節(jié).
四. 狀態(tài)2: 非規(guī)格數(shù)
對于非規(guī)格數(shù):?
符號位, 1位: 可正可負
指數(shù)位, 8位: 全為0
對于32位浮點數(shù)來說, 規(guī)格數(shù)的指數(shù)位全為0, 對應的值也是0. 偏置bias依舊是127, 但:
實際指數(shù)的計算方法是: 實際指數(shù) = 1 - bias = 1 - 127 = -126, 即非規(guī)格數(shù)的實際指數(shù)固定為-126. 注意這是規(guī)定.
其實我們可以發(fā)現(xiàn), 非規(guī)格數(shù)實際指數(shù)的計算方法(實際指數(shù) = 1 - bias), 和規(guī)格數(shù)實際指數(shù)的計算方法(實際指數(shù)? = 指數(shù)位的值 - bias)不同
后文會看到這樣規(guī)定的原因.
尾數(shù)位, 23位: 尾數(shù)位前隱藏的整數(shù)部分是0.?而非?1.
所以尾數(shù)位的取值范圍是[0.00000000000000000000000, 0.11111111111111111111111] (二進制)
換算為10進制為[0,1)
非規(guī)格數(shù)的作用: 表示0和靠近0的數(shù)
那么規(guī)格數(shù)是怎么完成這個任務的呢.?
首先看看非規(guī)格數(shù)是怎么表示0的:
依舊要用到我們的ieee754浮點數(shù)求值公式:
然后, 非規(guī)格數(shù)尾數(shù)的取值范圍是[0, 1), 指數(shù)固定為-126. 這就很簡單了, 讓尾數(shù)取0不就能表示數(shù)值0了:
可見當尾數(shù)取0時, 通過變更符號位, 我們可以表示出+0和-0, IEEE754規(guī)范中也確實存在著這兩種表示0的方式
注: 某些場景下, +0和-0會被認為完全相同, 某些場景下, +0和-0又被認為不完全相同. 這往往取決于具體的編程語言和應用場景, 此處不做討論. 只需知道IEEE754中可以表示+0和-0即可. +0和-0在IEEE754中是兩種內存狀態(tài)(符號位不同)
然后看看非規(guī)格數(shù)是怎么表示接近0的數(shù)的:
準確來說, 我們要看看, 對于32位浮點數(shù), 非規(guī)格數(shù)是怎么表示出之間的數(shù)的. 也就是如何表示出下圖中的紅色區(qū)域的:
其實也很簡單:
浮點數(shù)求值公式:
然后, 非規(guī)格數(shù)尾數(shù)的取值范圍是[0, 1), 指數(shù)固定為-126.
所以, 非規(guī)格數(shù)的取值范圍就是:
等于:
?
等于:
這樣就完成了...
現(xiàn)在我們嘗試著把32位浮點數(shù)中的非規(guī)格數(shù)的取值范圍, 和規(guī)格數(shù)的取值范圍拼接在一起
32位浮點數(shù)中, 非規(guī)格數(shù)的取值范圍:
32位浮點數(shù)中,?規(guī)格數(shù)的取值范圍:
仔細看一下, 啊...非規(guī)格數(shù)的取值范圍, 正好可以卡在規(guī)格數(shù)取值范圍的中間, 現(xiàn)在我們得到了一個完整的取值范圍:
感覺世界一下子清爽了起來.?
這就是非規(guī)格數(shù)的作用: 用于表示0和靠近0的數(shù), 用于和規(guī)格數(shù)"珠聯(lián)璧合", 形成一個完整的取值范圍.
不過這還沒有完...
五. 非規(guī)格數(shù)補充
1. 逐漸溢出
前文說過, 非規(guī)格數(shù)尾數(shù)的取值范圍是[0, 1), 指數(shù)固定為-126
所以是尾數(shù)的變化在導致非規(guī)格數(shù)的值變大, 舉例:
0 00000000 00000000000000000000001
就比
0 00000000 00000000000000000000000
要大一些
隨著尾數(shù)逐漸增大, 相應的非規(guī)格數(shù)也在不斷增大:
...
0 00000000 11111111111111111111111 這是非規(guī)格數(shù)的最大值
此時尾數(shù)(帶上隱藏的整數(shù)部分0.)其實是0.11111111111111111111111, 是個比1小一點點的數(shù), 不妨記做(1 - ε)
那, 此時非規(guī)格數(shù)的值就是?
好, 我們再往前前進一格, 此時會進入規(guī)格數(shù)的范圍:
0 00000001 00000000000000000000000
這是個規(guī)格數(shù),?
其尾數(shù)位的值: 其實隱藏了 1.?或者說, 此時真正的尾數(shù)應該是1.00000000000000000000000 , 也就是1
其指數(shù)位的值: 是1, 則實際指數(shù)應該是1 - bias = 1-127 = -126
所以這個規(guī)格數(shù)的值就是:?, 這是規(guī)格數(shù)的最小值.
注意到?jīng)]有: 非規(guī)格數(shù)的最大值是:?
規(guī)格數(shù)的最小值是:?
兩者之間實現(xiàn)了非常平滑的過度, 非規(guī)格數(shù)的最大值非常緊密的連接上了規(guī)格數(shù)的最小值
非規(guī)格數(shù) "一點點逐漸變大,? 最后其最大值平穩(wěn)的銜接上規(guī)格數(shù)的最小值", 這種特性在ieee754中被叫做逐漸溢出(gradual underflow)
明白了這一點, 就很容易想通:?
① 為什么規(guī)定非規(guī)格數(shù)的尾數(shù)前隱藏的整數(shù)部分是?0.??而規(guī)格數(shù)尾數(shù)前隱藏的整數(shù)部分是1.?
② 為什么非規(guī)格數(shù)的真實指數(shù)的計算公式是 1 - bias, 而規(guī)格數(shù)的真實指數(shù)的計算公式是 指數(shù)部分的值 - bias 了
仔細思考一下, 就是這些設計實現(xiàn)了逐漸溢出這種特性.?
↑ 關于第①點: 這使得非規(guī)格數(shù)的尾數(shù)取值范圍是[0,1), 而規(guī)格數(shù)的尾數(shù)取值范圍是[1,2), 兩者平滑的銜接在了一起
↑ 關于第②點:?這使得對于32位浮點數(shù)來說, 非規(guī)格數(shù)的真實指數(shù)固定為-126, 而規(guī)格數(shù)的指數(shù)是[-126, 127], 兩者也平滑的銜接在了一起...
2. 密集分布
第三章中我們說過: 如果把ieee754浮點數(shù)想象成一個表盤的話, 那表盤上的藍點是越來越稀疏的. 或者說越靠近0越密集.
不過當時僅討論了規(guī)格數(shù)的分布情況, 那非規(guī)格數(shù)呢.
答案是, 非規(guī)格數(shù)的藍點分布間隔, 和規(guī)格數(shù)中藍點最密集的區(qū)域(也就是最靠近0的區(qū)域)一致, 可以驗證一下:
非規(guī)格數(shù): 范圍是?在這個范圍中分布了2^23個藍點, 則藍點間的間隔是
規(guī)格數(shù)中藍點最密集的區(qū)域, 也就是最靠近0的區(qū)域是:?, 在這個范圍中分布了2^23個藍點, 則藍點間的間隔是
所以, 即便把非規(guī)格數(shù)與規(guī)格數(shù)放在一起審視, ieee754浮點數(shù)表盤上的藍點依舊是越靠近0越密集, 越靠近∞越稀疏
下面是在c語言中的測試結果:
六. 狀態(tài)3: 特殊數(shù)
特殊數(shù)分為兩種: 無窮和NaN
1. 先說無窮
理解了非規(guī)格數(shù), 再理解無窮就很簡單了, 兩者有很多相似之處:
對于無窮:?
符號位, 1位: 可正可負
指數(shù)位, 8位: 全為1
尾數(shù)位, 23位: 全部為0
當內存位于上述狀態(tài)時, 就表示無窮(infinity)
具體寫出來就是: * 11111111 00000000000000000000000 用于表示無窮(infinity)
其中符號位可正可負, 分別記做+infinity和-infinity
以32位浮點數(shù)為例, 其規(guī)格數(shù)的取值范圍是:?
當要存儲的數(shù)大于規(guī)格數(shù)取值范圍的最大值時, 就會被記做+infinity, 比如2^128, 剛剛超過規(guī)格數(shù)的取值范圍的最大值, 就會被記做+infinity
當要存儲的數(shù)小于規(guī)格數(shù)取值范圍的最小值時, 就會被記做-infinity, 比如-2^128, 剛剛小于規(guī)格數(shù)的取值范圍的最小值, 就會被記做-infinity
需要注意的是: 所有+infinity的內存狀態(tài)都是0 11111111?00000000000000000000000, 不會有任何變動
2^128對應的內存狀態(tài)是0 11111111?00000000000000000000000
2^123456789對應的內存狀態(tài)還是0 11111111?00000000000000000000000
同理, -infinity的內存狀態(tài)都是1?11111111?00000000000000000000000
此外: 就像非規(guī)格數(shù)的最大值可以和規(guī)格數(shù)的最小值平穩(wěn)銜接一樣, 規(guī)格數(shù)的最大值也可以和+infinity平穩(wěn)銜接:
規(guī)格數(shù)的最大值是: 0 11111110 11111111111111111111111
尾數(shù)位其實是1.11111111111111111111111, 非常接近2, 不妨記做2-ε
指數(shù)是127
所以最大值是:?
+infinity的內存狀態(tài)則是: 0 11111111?00000000000000000000000
尾數(shù)其實是:?1.00000000000000000000000, 等于1
指數(shù)是128
所以+infinity的內存狀態(tài)對應的值是:?
可見規(guī)格數(shù)的最大值也能和+infinity平穩(wěn)銜接. -infinity同理.
現(xiàn)在我們就集齊了整個數(shù)軸:
↑ 而且各個節(jié)點都能平穩(wěn)的銜接在一起
2. NaN
NaN則更簡單, 前面說過, 如果計算出來的值不是一個數(shù)值, 則記錄為NaN
NaN的內存狀態(tài)是:?
符號位, 1位: 可正可負
指數(shù)位, 8位: 全為1
尾數(shù)位, 23位: 不全為0即可
僅僅是一種特殊狀態(tài)標記而已.
需要注意的是, 根據(jù)wiki, 沒有+NaN或-NaN這種說法, 統(tǒng)稱為NaN
七. 總結
本章介紹了IEEE754規(guī)范中的非規(guī)格數(shù), 特殊值(±infinity, NaN), 包括它們的內存狀態(tài), 作用, 工作原理等.
下一章會先偏離一下主線, 補充一些之前沒有提到的瑣碎知識點.?
下一章再見吧~