深入理解浮點數的表示

AH20發表於2024-10-18

浮點數的表示

通常,浮點數表示為:

\[N = (-1)^{S} \times M \times R^{E} \]

其中,S取值為0或者1,用來決定浮點數的符號;M是一個二進位制定點小數,稱為尾數,一般用定點原碼小數表示;E是一個二進位制頂點整數,稱為階碼或者指數,用移碼錶示。R是基數(隱含),可以約定為2、4、16等

浮點數的表示範圍

原碼是關於原點對稱的,故浮點數的範圍也是關於原點對稱的,如圖2.10所示

img

  • 運算結果大於最大正數時稱為正上溢,小於絕對值最大負數時稱為負上溢,正上溢和負上溢統稱上溢。資料一旦產生上溢,計算機必須中斷運算操作,進行溢位處理。
  • 當運算結果在0至最小正數之間時稱為正下溢,在0至絕對值最小負數之間時稱為負下溢,正下溢和負下溢統稱下溢。資料下溢時,浮點數值趨於零,計算機將其當作機器零處理。

浮點數的規格化

為了在浮點數運算過程中儘可能多地保留有效數字的位數,使有效數字儘量佔滿尾數數位,必須在運算過程中對浮點數進行規格化操作。所謂規格化操作,是指透過調整一個非規格化浮點數的尾數和階碼的大小,使非零浮點數在尾數的最高數位上保證是一個有效值。

  • 左規:當運算結果的尾數的最高數位不是有效位,即出現 \(\pm\) 0.0...0x...x的形式時,需要進行左規。左規時,尾數每左移一位、階碼減1(基數為2時)。左規可能要進行多次。

  • 右規:當運算結果的尾數的有效位進到數點前面時,需要進行右規,右規只需進行一次。將尾數右移一位、階碼加1(基數為2時)。規時,階碼增加可能導致溢位。

img

IEEE754標準

img

img

單精度格式中包含 1 位符號 s、8 位階碼e和 23 位尾數,:雙精度格式包含1位符號 s、11位階碼e和 52 位尾數 1。基數隱含為2:尾數用原碼錶示。對於規格化的二進位制浮點數,尾數的最高位總是 1,為了能使尾數多表示一位有效位,將這個1隱藏,稱為隱藏位,因此 23 位尾數實際表示了 24 位有效數字。IEEE 754 規定隱藏位1的位置在小數點之前,例如,\((12)_{10} = (1100)_{2}\),將它規格化後結果為 \(1.1 \times 2^{3}\),其中整數部分的“1”將不儲存在 23 位尾數內。

單精度與雙精度浮點數都採用隱藏尾數最高位的方法,因而使浮點數的精度更高。

在 IEEE 754 標準中,指數用移碼錶示,但偏置值並不是通常n位移碼所用的\(2^{n-1}\),而是 \(2^{n-1}-1\)因此,單精度和雙精度浮點數的偏置值分別為 127 和 1023。在儲存浮點數階碼之前,偏置值要先加到階碼真值上。

所以,在IEEE754標準中,規格化單精度浮點數的真值為:

\[(-1)^{s} \times 1.f \times 2^{e-127} \]

規格化雙精度浮點數的真值為

\[(-1)^{s} \times 1.f \times 2^{e-1023} \]

浮點數能表示的範圍

img

這個表的意思應該是絕對值的最大值與最小值

階碼為全0或者全1時的特殊意義

img

img

定點、浮點表示的區別

  • 數值的表示範圍

    • 若定點數和浮點數的字長相同,則浮點表示法所能表示的數值範圍遠大於定點表示法。
  • 精度

    • 雖然擴大了數的表示範圍,但精度降低了。對於字長相同的定點數和浮點數來說,精度卻降低了
  • 數的運算

    • 浮點數包括階碼和尾數兩部分,運算時不僅做尾數的運算,還要做階碼的運算,而且運算結果要求規格化,所以浮點運算遠比定點運算複雜
  • 溢位問題

    • 在定點運算中,當運算結果超出數的表示範圍時,發生溢位;在浮點運算中,運算結果超出尾數表示範圍卻不一定溢位,只有規格化後階碼超出所能表示的範圍時,才發生溢位。

浮點數的機器數

最後我們附上一個將浮點數轉為二進位制的C++程式,結合真值來看,可以對浮點數的編碼有更好的理解


#include <iostream>
#include <bitset>

int main() {
    float num;
    std::cout << "Please enter a floating-point number: ";
    std::cin >> num;

    union FloatInt {
        float f;
        unsigned int i;
    } data;

    data.f = num;

    // Using union to access the internal representation of the floating-point number
    std::cout << "The machine representation of the floating-point number " << num << " (in hexadecimal): "
              << std::hex << data.i << std::endl; // Print the hexadecimal form

    // Convert the integer to a 32-bit binary string
    std::bitset<32> binaryRepresentation(data.i);
    std::string binaryString = binaryRepresentation.to_string();

    // Splitting into sign, exponent, and mantissa parts
    std::string sign = binaryString.substr(0, 1);
    std::string exponent = binaryString.substr(1, 8);
    std::string mantissa = binaryString.substr(9, 23);

    // Printing the sign, exponent, and mantissa separately
    std::cout << "The machine representation of the floating-point number " << num << " (in binary): " 
              << sign << ";" << exponent << ";" << mantissa << std::endl;

    return 0;
}

相關文章