單精度浮點數的取值,表示以及相關
取值範圍及精度
可以表示的範圍為±3.40282 * 10^38(1.1111…1×2^127)即:
0-11111110-11111111111111111111111(23個1)
單精度浮點數可以表示1.175 * 10-38(1.00…0×2^-126)的資料而不損失精度。
0-00000001-00000000000000000000001(22個0,最後一位是1)
浮點數最小能表示的是當階碼都是0時,表示2^-126*0.fractionbits
ps:以上圖片是從 這個網址 擷取。
表示方式
- 如果指數位全零,尾數位是全零,那就表示0
- 如果指數位全零,尾數位是非零,就表示一個很小的數(subnormal),計算方式 (−1)^signbit × 2^−126 × 0.fractionbits(注意這裡是0.fractionbits,應該是為了和階碼是-126的時候做出區分,其實也就是比-126的時候能表示的數更小了)
- 如果指數位全是1,尾數位是全零,表示正負無窮
- 如果指數位全是1,尾數位是非零,表示不是一個數NAN
- 剩下的計算方式為 (−1)^signbit × 2^(exponentbits−127) × 1.fractionbits
補碼
到底什麼是補碼,一直到看了 這個知乎回答 之前,我對補碼的概念就是反碼加一,而且也沒有想過到底為什麼這樣,有什麼意義,看完之後才有些恍然大悟。
首先,第一個問題,補碼是用來做什麼的?
補碼是用來方便ALU做減法運算的,因為補碼是沒有符號的,減一個數相當於加這個數的補碼。
所以,第二個問題就是,為什麼減一個數相當於加一個數的補碼呢?
在回答這個問題之前,首先問一下,如果補碼就是簡單的反碼加一為什麼要叫補碼,為什麼不直接叫反碼加一呢,這裡就要提出一個概念,叫補數
鐘錶的例子
這個例子就是補數的直觀理解,先假定錶盤就表示0-11點,然後現在指標指向2點,如果我想將指標撥到3點有兩個辦法,第一個是順時針撥1個格,第二個是逆時針撥11個格,在一個滿刻度是12的錶盤上,這兩個的效果是一樣的。抽象來講,2 - 1 = 2+11
這是為什麼呢,這就有個模的概念,模,當然數學上有抽象解釋,我們這裡就可以理解成數的表示極限大小,這裡的模就是12,而對於十進位制的兩位數來說,模就是100。
繼續回到 2 - 1 = 2+11 這個問題,因為模是12,所以在這個模下,每個數有其補數,補數的意思就是模減其本身。這裡1的補數就是 12 - 1 = 11.而減去一個數,就相當於加上這個數的補數,所以我們得到2 - 1 = 2+11。
再以十進位制兩位數為例,90 - 10 = 80.10的補數是100-10=90,所以 90 -10 = 90 + 90(忽略百位,因為這裡只有兩位)。到這裡總結一下,一個數的補數 = 模 - 這個數,一個數 - 另一個數 = 一個數 + 這個數的補數
但是如果是10 - 90 怎麼辦,難道10 - 90 = 10 + 10,-80難道等於20?沒錯!我們想用數表示-80,卻不讓加負號,那就直接讓-80 = 20。所以,一個負數就用它絕對值的補數來表示。
那麼,現在的問題是,一個數既可以表示正數又可以表示負數,比如20既可以表示-80也可以表示20,那咋整,就規定0~99,0~49表示整數,50~99表示負數,90就代表-10這種。
實際上
對於浮點數的階碼是8位二進位制數,其表示的極限是256(11111111表示255),所以模就是256,根據上面講過的,將表示範圍一分為二:00000001~01111111表示正數,10000000~11111110表示負數(全0和全1有特殊含義)。這樣結合上面講的知識就顯而易見了,以10000000為例,256 - |x| = 128.所以表示的x=-128
移碼
雖然補碼解決了負數的問題,但是補碼還是有一定的缺陷,就是比較大小不方便,而進行浮點數運算的時候,有一步是對階,也就是比較階碼的大小然後再獲得浮點數實際大小。為了方便比較大小,浮點數使用移碼錶示階碼。
移碼,顧名思義,就是當前碼通過(在座標軸上)移動之後獲得的碼,而移動的距離稱為偏置(bias)。
為什麼移動之後就方便比較大小呢,具體如下圖:
不用移碼的時候數軸是這樣的:
上面是實際的數,而下面是這些數代表的數,也就是上面講的,254代表-2之類的。
如果我們使得下面的數向右移動一格:
可以看到,現在254代表的是-3了,再移動一格呢:
依次類推,可以一直推到255代表的是最大的正數,這樣,就可以直接通過比較碼的大小來判斷實際值的大小了,是不是很方便呢
不過這裡沒有考慮全0和全1的情況,但是大概原理就是這樣了。
ps:為什麼為什麼用127做偏置而不是128:據說是為了讓數的表示範圍對稱( 原文 ),但是感覺比較牽強而且也不比用128時對稱
半精度與單精度的轉換
主要是最近在研究f16和f32的轉換才看了上面一堆東西,正題是f16和f32是怎麼轉換的。
這個就簡單了,由上面的知識可以推知,half的表示範圍最大也就到65535,而float則很大,因此當half往float轉換時,就是指數位轉換到指數位,小數位低13位補零。當然考慮到階碼是移碼,因此要-15+127才是最終的階碼。同理,從float轉換到half也是,階碼-127+15,然後砍掉小數位後13位即可。
這裡要注意,如果float原本表示的數超過了half的表示範圍,那麼轉換成的half就是階碼全是1的NaN。
cite:Van Der Zijp J. Fast half float conversions[R]. Working paper, 2012ftp://www. fox-toolkit.org/pub/fasthalffloatconversion. pdf, 2008.