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


IEEE 浮點表示

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

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

將浮點數的位分為 3 個部分:

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

以 C 語言為例,不同的精度下,s、exp、frac 有不同的位數:

  單精度:
  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. 規格化

    +-------------------------------------------------------+
    | 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

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

  2. 非規格化

    +-------------------------------------------------------+
    | 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)
      +-------------------------------------------------------+
      | s |     exp=255       |         frac=0                |
      +-------------------------------------------------------+
      
      exp 所有位皆是 1,frac 所有位皆是 0 ,表示無窮大
      s = 1 時表示負無窮大,s = 0 時表示正無窮大
    • NaN(Not a Number)
      +-------------------------------------------------------+
      | 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)));
    }
}

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM