浮點數組成
在計算機中浮點數通常由三部分組成:符號位、指數位、尾數位。IEEE-754中32位浮點數如下:
上圖32bit浮點數包含1bit的符號位,8比特的指數位和23bit的尾數位。對于一個常規浮點數,我們來看看它是如何存儲和計算的。這里以浮點數25.125為例。這個浮點數分為整數(25(d))和小數部分(0.125(d)),(下面25(d)中d表示十進制,后續b表示二進制)
于是:
25 ( d ) = 11001 ( b ) 0.125 ( d ) = 0.001 ( b ) 25.125 ( d ) = 11001.001 ( b ) = 1.1001001 E 4 ( b ) \begin{align*} 25(d)&=11001(b)\\ 0.125(d)&=0.001(b)\\ 25.125(d)&=11001.001(b)=1.1001001E^{4}(b) \end{align*} 25(d)0.125(d)25.125(d)?=11001(b)=0.001(b)=11001.001(b)=1.1001001E4(b)?
明顯這個數是一個正數,所以我們可以得知符號位S=0。指數位的計算和我們想象的稍微有點區別,這里我們的2禁止指數位是4。在十幾種考慮的指數位可能是負數,為了避免負數情況,我們可以將指數表達范圍移動一個偏置到正數區域。因為我們的指數位位8bit,有符號整數最高能表示 2 7 ? 1 = 127 2^7-1=127 27?1=127,所以對指數位偏移一個127即可得到正數。所以我們的指數位部分為: 4 ( d ) + 127 ( d ) = 131 ( d ) = 10000011 ( b ) 4(d)+127(d)=131(d)=10000011(b) 4(d)+127(d)=131(d)=10000011(b)。接下來是尾數,因為 25.125 ( d ) = 11001.001 ( b ) 可以為 1.1001001 E 4 ( b ) 也可以為 . 11001001 E 5 ( b ) 25.125(d)=11001.001(b)可以為1.1001001E^{4}(b)也可以為.11001001E^{5}(b) 25.125(d)=11001.001(b)可以為1.1001001E4(b)也可以為.11001001E5(b)這樣我們就得到了不同的表示方法。為了確保總是用相同的方法表示浮點數,IEEE-754中要求了表示尾數的部分總是為1.xxx。正因如此,我們這里的指數部分才是4而不是5。也正是因為如此,所以我們只需要保存.xxx即可,因為小數點前一定是1,這樣能節省一個bit。尾數部分為1001001,這樣我們的浮點數在內存中表示為:01000001110010010000000000000000。這個值如果是32有符號的定點數int32他應該表示的為:1103691776。
0 10000011 10010010000000000000000
代碼驗證
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <limits>using namespace std;
// 定義一個聯合體用于訪問浮點數的內存表示
union FloatBits {float f;uint32_t bits;
};// 打印浮點數的二進制表示
void printFloatBits(float value) {FloatBits fb;fb.f = value;std::cout << "Float value: " << std::fixed << std::setprecision(6) << value<< std::endl;std::cout << "Binary representation: ";// 從最高位開始逐位打印for (int i = 31; i >= 0; --i) {// 通過位掩碼檢查每一位的值uint32_t mask = 1 << i;std::cout << ((fb.bits & mask) ? '1' : '0');// 在輸出中添加空格分組if (i % 8 == 0)std::cout << ' ';}std::cout << std::endl;
}int main() {float number = 25.125f;int a = 1103691776;float *b = reinterpret_cast<float *>(&a);int zp = 0; // 00000000000000000000000000000000int zn = -2147483648; // 10000000000000000000000000000000int infn = -8388608; // 11111111100000000000000000000000int infp = 2139095040; // 01111111100000000000000000000000int nan = 2139095041; // 01111111100000000000000000000001float inf_float = -std::numeric_limits<float>::infinity();std::cout << "-inf float for int " << *reinterpret_cast<int *>(&inf_float)<< " -inf float = " << inf_float << "\n";float *zero_pos = reinterpret_cast<float *>(&zp);float *zero_neg = reinterpret_cast<float *>(&zn);float *infn_f = reinterpret_cast<float *>(&infn);float *infp_f = reinterpret_cast<float *>(&infp);float *nan_f = reinterpret_cast<float *>(&nan);printFloatBits(number);std::cout << "a = " << a << " *b = " << *b << " number = " << number<< " +0 => " << *zero_pos << " -0 =>" << *zero_neg << " +inf => "<< *infp_f << " -inf => " << *infn_f << " nan => " << *nan_f<< "\n";return 0;
}
使用GDB驗證存儲變量:
x/4tb:
- 4 :表示4個后面的元素
- t:表示打印為二進制
- b:打印單位為byte(8bit)。
你可能會感到疑惑為什么這個值看起來和我們的結果不太一樣,這是因為我們的機器使用小端存儲法。show endian可以打印當前運行機器上是大端存儲還是小端存儲法。實際的二進制按照高位字節存儲在低位的方式存儲。所以這個值作為二進制,我們應該反向理解為:0100000 111001001 00000000 00000000。同理你可以試一試打印變量a,你就會發現兩著結果完全相同。盡管二進制上完全相同,但是因為用了不同的類型符修飾運算的時候依然能知道這個數是表示浮點數的25.125還是無符號整數的1103691776。
浮點數的特殊值
- E不全為0或不全為1。這時,浮點數就采用偏置表示,即指數E的計算值減去127(或1023),得到真實值,再將有效數字M前加上第一位的1。
- E全為0。這時,浮點數的指數E等于1-127(或者1-1023(64bit)),有效數字M不再加上第一位的1,而是還原為0.xxxxxx的小數。這樣做是為了表示±0,以及接近于0的很小的數字。
- E全為1。這時,如果有效數字M全為0,表示±無窮大(正負取決于符號位s);如果有效數字M不全為0,表示這個數不是一個數(NaN)(111111111000000000000000000000000(b))
如何計算負數的二進制
- 找到對應的正數(8388608),計算二進制(00000000100000000000000000000000)。
- 反轉所有位,得到二進制的反碼(11111111011111111111111111111111)。
- 反碼+1得到二進制的補碼(111111111000000000000000000000000)。