一、單精度浮點數
先來簡單了解一下浮點數在計算機中的存儲方式。根據IEEE 754標准,單精度浮點數格式如下(所有位取0):
| 符號位 |
指數部分 |
尾數 |
|||||||||||||||||||||||||||||
| 0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
各部分解釋
單精度浮點數有32個二進制位,左側是高位,右側是低位。最高位被指定為符號位,0代表正數,1代表負數。指數部分將是2的冪次,其編碼值(即上表指數部分對應的八個二進制位)規定為指數的實際值加上偏移值2^7-1=127,這是為了避免負數,將[-127, 128]映射到[0, 255],這樣指數部分編碼就可以簡單地編排為[00000000, 11111111]。例如指數部分為00001000,十進制為8。那么其所代表的實際指數是8-127=-119,即要乘上2-119。最后23位尾數是不包含整數位的實際有效小數位。規約數的整數位是1,非規約數的整數位是0。
規約形式的浮點數與非規約形式的浮點數
指數部分的編碼值在[1, 2e-2]內,且尾數部分的整數位是1,這樣的浮點數被稱為規約形式的浮點數。
指數部分的編碼值為0,尾數非零,這樣的浮點數被稱為非規約形式的浮點數。
規約浮點數的尾數∈[1, 2),而非規約浮點數的尾數∈(0, 1)。 需要注意,非規約數指數編碼為00000000,但指數實際值是-126,而非-127。非規約浮點數被IEEE 754-1985標准采用是因為它的漸進式下溢出,而規約浮點數將導致突然式下溢出,具體原理不再展開。
實際計算
設符號位為s。sign(s)確定正負:sign(0)=1,sign(1)=-1;指數部分為e;尾數部分為f。用(N)2表示二進制數N。
規約形式:sign(s)*2e-127*(1.f)2
非規約形式:sign(s)*2-126*(0.f)2
特殊值和極值
| 類別 |
符號位 |
指數部分 |
尾數 |
數值 |
|||||||||||||||||||||||||||||
| 正負零 |
0/1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
±0 |
| 正負無窮 |
0/1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
±∞ |
| 最大規約數 |
0/1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
0 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
±(2−2-23) × 2127 ≈ ±3.40e38 |
| 最大非規約數 |
0/1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
±(1−2−23) × 2-126 ≈ ±1.18e-38 |
| 最小規約數 |
0/1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
±2−126 ≈ ±1.18e-38 |
| 最小非規約數 |
0/1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
±2−23 × 2−126 = ±2−149 ≈ ±1.40e-45 |
| NaN |
0/1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
非零 |
NaN |
||||||||||||||||||||||
上下溢出值
由浮點數的存儲方式可以看出計算機所能表示的浮點數是有限的,我們把所能表示的最大正值稱為上溢值,而把最接近0的正值稱為下溢值。由表二我們看到上溢值為±3.40e38,下溢值為±1.40e-45。
二、機器精度
Wikipedia上機器精度Machine Epsilon是這樣描述的:“Machine epsilon gives an upper bound on the relative error due to rounding in floating point arithmetic”。因為浮點數是離散的,所以實數的表示存在着誤差。例如圓周率這樣的無限不循環小數不可能精確地由某一個浮點數表示。
我們需要一些具體的量去刻畫這種誤差,以估計結果的准確性。機器精度便是其中之一:它是所有相對誤差的上限。相對誤差是絕對誤差與精確值的比值的絕對值。例如一個精確的實數x,所有單精度浮點數中與x距離最近的數為y,絕對誤差為|y-x|,相對誤差即\(|y-x| \over |x|\),而所有相對誤差的上限便是單精度浮點數的機器精度。
對於32位浮點數,指數8位,尾數為23位。對於兩個指數實際值為E的相同的浮點數,若它們尾數部分相差(00000000000000000000001)2,即2-23,易見它們是相鄰的。那么與它們指數相同的實數x與距x最近的浮點數y之間的距離|y-x|一定小於此相鄰兩浮點數的距離2-23 * 2E。可以取x=1.0(或者其他任何數),此時實際指數為0,所以機器精度是\(2^{-23} \times 2^{0} \over 1.0\)。
三、C++程序實現
利用庫求值
標准庫<limits>中的numeric_limits類中包含了許多算數特殊值:
- 上溢值: std::numeric_limits<float>::max();
- 規約下溢值: std::numeric_limits<float>::min();
- 非規約下溢值: std::numeric_limits<float>::denorm_min();
- 機器精度: std::numeric_limits<float>::epsilon();
其中numeric_limits<float>中float可以換成int,double等其它類型。
實際二進制存儲值
std::string get_binary(float f) { int index_byte, index_bit; unsigned int byte = 0; char ch, *p; std::string bin_f = ""; p = (char *)(&f); for (index_byte = sizeof(float)-1; index_byte>=0; index_byte--) { ch = *(p+index_byte); //從最高位開始取 byte = ch; //將地址中8個二進制位賦值成十進制數 for (index_bit = 1; index_bit<=8; index_bit++) { if (byte >=128) bin_f += "1"; else bin_f += "0"; //判斷首位是1還是0 byte <<= 1; //將當前位變成首位 byte &= 255; //確保始終8個二進制位 } } return bin_f; }
