計算機中浮點數的表示形式

IEEE 浮點表示

IEEE 浮點標準:V = (-1)s * M * 2E 表示一個浮點數:html

  • 符號(sign) s 決定 V 的正(s=0)或負(s=1),對於 0 後面會有說明
  • 尾數(Mantissa) 二進制小數
  • 階碼(Exponent) E 的做用是對浮點數加權,這個權重是 2 的 E 次冪

將浮點數的位分爲 3 個部分:java

  • 1 位的符號位 s 表示 s
  • k 位的階碼字段 exp = ek-1...e1e0 表示 E
  • n 位的小數字段 frac = fn-1...f1f0 表示 M

以 C 語言爲例,不一樣的精度下,s、exp、frac 有不一樣的位數:api

單精度:
  31  30      23 22                                       0
  +---+---------+-----------------------------------------+
  | s |   exp   |             frac                        |
  +---+---------+-----------------------------------------+
  雙精度:
  63  62             52 51                               32
  +---+----------------+----------------------------------+
  | s |       exp      |           frac(51:32)            |
  +---+----------------+----------------------------------+
  31                                                      0
  +-------------------------------------------------------+
  |                     frac(31:0)                        |
  +-------------------------------------------------------+
  • float:s 1位、exp 的 k=8位、frac=23位,合計 32位
  • double:s 1位、exp 的 k=11位、frac=52位,合計 64位

分類

以 C 語言單精度爲例,根據 exp 存儲的位的不一樣,所表示的浮點數能夠分紅 3 中不一樣的狀況,而最後一種狀況中狀況分兩個變種:數組

  1. 規格化post

    +-------------------------------------------------------+
    | s | exp!=0 & exp!=255 |         frac                  |
    +-------------------------------------------------------+
    這是最多見的狀況,exp 的位模式既不爲全 0,也不爲全 1
    規格化的值有兩點須要特別注意:
    1. 階碼 E 包含必定的偏置 Bias,也就是說E = exp - Bias,exp 是無符號數,Bias = 2k-1 - 1,偏置的做用是爲了在規格化取值範圍與非規格化取值範圍之間平滑過分
    2. 尾數 M 的值並非 frac 所表示的小數值,實際狀況是M = 1 + frac
      一般狀況下,二進制整數部分經過調整小數點(也就是修改 E)來變成 1,因此 IEEE 的表示法直接將這一位的 1 省去,這樣二進制小數部分就能多存儲一位,提升了精度,也就是說這個 frac 隱含了開頭的 1.net

      舉個例子:假設 frac 有 5 位,如今要存儲一個二進制數 b,b 的值是 0.101011(2),調整一下權重:1.01011 * 2-1(2),farc 存儲的就是小數點後面的這 5 位01011code

  2. 非規格化htm

    +-------------------------------------------------------+
    | s |     exp=0         |         frac                  |
    +-------------------------------------------------------+
    exp 位模式全爲 0,E = exp - Bias,M = frac
    規格化數因 frac 隱含開頭的 1,M >= 1,故而沒法表示 0 這個數。
    當 exp 和 frac 都是 0 時,s = 1 獲得 -0.0,s = 0 獲得 +0.0
    • 無窮大(Infinity)blog

      +-------------------------------------------------------+
      | s |     exp=255       |         frac=0                |
      +-------------------------------------------------------+
      exp 全部位皆是 1,frac 全部位皆是 0 ,表示無窮大
      s = 1 時表示負無窮大,s = 0 時表示正無窮大
    • NaN(Not a Number)ci

      +-------------------------------------------------------+
      | s |     exp=255       |         frac!=0               |
      +-------------------------------------------------------+

      exp 全部位皆是 1,frac 的位模式不全爲 0 ,表示 NaN 不是一個數

階碼的值決定了該浮點數是規格化的、非規格化的、無窮大或者 NaN

轉換示例

舉例,將 1 個 float 數據轉換爲 4Byte 的二進制數據存儲起來:

float a = 10.25F;

                 Decimal       Binary 
整數部分:          10    ====>  1010
小數部分:         0.25   ====>  0.01  
科學計數法: 1.025 * 10^1 ====>  1010.01 = 1.01001 * 2^3

s = 0           ====> 0
E = 3           ====> E 包含偏置,IEEE 用 0111 1111 = 2^7 - 1 = 127 來表示 E = 0,因此當 E = 3 時,二進制表示爲: 1000 0010 = 130 = 127 + 3
M = 1.01001     ====> 小數點前是 1,因此直接去掉,只保留小數部分,二進制表示爲:0100 1000
            s      exp              frac
10.25 ====> 0 | 1000 0010 | 01001000000000000000000

      ====> 0100 0001 0010 0100 0000 0000 0000 0000

須要注意的 3 個地方:

  1. 小數部分:0.25 = 1/4,即 2 的 -2 次方,也就是二進制的 1 小數點左移 2 位,因此獲得的二進制表示爲 0.01。須要注意的是:不是全部的十進制小數都能轉換爲二進制小數,好比十進制的 0.3,因此只能取近似值,經過 2^-n(n>0,n 爲整數) 來接近 0.3

  2. 規格數:基數爲 2,位數最高位爲 1 的數爲規格數,此時可以表示的數據精度最高。經過階碼的大小來控制小數點的位置使得尾數變爲規格數的過程,稱爲規格化。在存儲時尾數只保存了小數部分。可是規格數沒法表示 0
  3. 階碼是有偏置的:2k-1 - 1

Java 中的應用

將給定的字節數組轉換爲對應的浮點數,JDK 中 Float 和 Double 均提供了對應的方法來處理這種狀況,以 Float 爲例,使用的方法java.lang.Float#intBitsToFloat(int bits),將參數 bits 的位模式解析爲浮點數,API 中的說明:

int s = ((bits >> 31) == 0) ? 1 : -1;
int e = ((bits >> 23) & 0xff);
int m = (e == 0) ?
                (bits & 0x7fffff) << 1 :
                (bits & 0x7fffff) | 0x800000;

浮點結果等於算術表達式 s·m·2e-150 的值
對應給定的浮點數,進行四捨五入可使用java.math.BigDecimal#setScale(int, int)方法,能夠設定保留小數的位數,以及四捨五入的方式

class Scratch {
    public static void main(String[] args) {
        int intbis = 0B0100_0001_0010_0100_0000_0000_0000_0000;
        System.out.println(Integer.toBinaryString(intbis));
        System.out.println(Float.intBitsToFloat(intbis));
        System.out.println(Integer.toBinaryString(Float.floatToIntBits(10.25F)));
    }
}

參考

相關文章
相關標籤/搜索