目錄結構:
IEEE 754(Institute of Electrical and Electronics Engineers)在1985年發布,該標准是為了統一規范浮點數的存儲。
1.浮點數的存儲過程
在IEEE 754標准中浮點數由三部分組成:符號位(sign bit),有偏指數(biased exponent),小數(fraction)。浮點數分為兩種,單精度浮點數(single precision)和雙精度浮點數(double precision),它們兩個所占的位數不同。
單精度浮點數(共32位):
1個符號位
8個指數位
23個小數位
雙精度浮點數(共64位):
1個符號位
11個指數位
52個小數位
接下來筆者以單精度浮點數0.15625講解浮點數的存儲過程:
0.1562510轉化為二進制就是0.001012,然后將該數寫成科學計數法(scientific notation),根據IEEE 754的規定,小數點的左邊只能有一個1,所以最終的科學計數法形式是:
0.1562510 = 0.001012 = 1.012 * 10-3
然后就可以得到小數部分為.012,指數部分為-3。
最終在內存中的存儲結果就是如下圖:
符號位(sign):0,因為該數是正數(1表示負數)。
有偏指數(biased exponent):-3 + 偏移量(bias),在單精度浮點數中偏移量是127,因此127+(-3)=124,所以偏移指數是124。在雙精度浮點數中偏移量是1023,因此偏移指數是1020。
小數(fraction):.010000000000000000000002
在上面已經展示了浮點數的存儲過程,接下來再仔細說一說有偏指數,還是拿單精度浮點數來說吧! 在單精度浮點數中,有8位可以用來存儲指數(范圍就是:0~255),那么怎么表示負的指數呢?IEEE 754標准的制定者為了解決這個問題,約定了指數偏移量(單精度的偏移量是127),指數值要在加上偏移量后才能進行存儲,這樣就能表示指數的正負值了。通常情況下,如果存儲的值大於偏移量,那么就意味着指數是正的;如果存儲的值小於偏移量,那么就意味着指數是負的;如果存儲的值等於偏移量,那么就意味着指數為0。
下面的對應關系,顯示了有偏指數代表的各種含義:
0 == 特殊情況:零(zero) 或 次正規數(subnormal)
1 == 2 ^ -126
...
125 == 2 ^ -2
126 == 2 ^ -1
127 == 2 ^ 0
128 == 2 ^ 1
129 == 2 ^ 2
...
254 == 2 ^ 127
255 == 特殊情況:無窮大(infinity) 或 非數值(NaN)
1.1 次正規數(Denormalized Number)
IEEE 754的設計者注意到,除了0.0所有的二進制的科學計數法都有一個1在小數點的左邊。在上面也提到過,在寫成標准的科學計數法的形式后,小數點的左邊只能有一個1。
比如:
25.010 == 110012 = 1.10012 * 24
0.62510 == 0.1012 = 1.012 * 2-1
小數點的左邊都是以一個1開始的,為了節約內存,它們規定:所有數在小數點左邊默認有一個1。
按照這個規定的話,那么能夠表示的最小正數就是:
0 00000001 000000000000000000000012 = 1.000000000000000000000012 * 2-126
如果指數全為0,只能表示數字0的話,那么表示小數位的23位就沒有利用起來。於是IEEE754的設計值,規定了一種新的數 次正規數(Subnormal Number Or Denormalize Number)。規定如下:
如果指數位全為0的話,那么在科學計數法中小數點的左邊就默認為一個0。這樣的數,就被稱為次正規數。
在次正規數中所有的偏移指數位都是0,於是規定在單精度浮點數中指數應該為-126(並非-127),在雙精度浮點數中指數應該為-1022(並非-1023)
所以最小的正數就應該是:
0 00000000 000000000000000000000012 = 0.000000000000000000000012 * 2-126
1.2 零(zero)
數值0被特殊表示:
符號位(sign) = 0或1
有偏指數(biased exponent) = 0
小數(fraction)= 0
0的內存二進制碼為:
0 00000000 00000000000000000000002
1 00000000 00000000000000000000002
1.3 非數值(NaN)
有一些算數操作是非法的,比如對負數開根號。這類非法操作被稱為浮點數異常(floating-point exception),異常結果由特殊字符NaN(Not a Number)表示。
符號位(sign) = 0或1
有偏指數(biased exponent)= 所有位都是1
小數(fraction) = 除了所有位都是0的數(因為所有為0,表示無窮大)
小數位只要不全為0,就表示非數值。
0 11111111 111111111111000000100002
或
1 11111111 111111111111000000100002
1.4 無窮大(infinity)
無窮大有兩種,正無窮大(Positive Infinity)和負無窮大(Negative Infinity)。
符號位(sign) = 0表示正無窮大,1表示負無窮大。
有偏指數(biased exponent) = 所有位都是1
小數(fraction) = 所有位都是0.
正無窮大
0 11111111 000000000000000000000002
負無窮大
1 11111111 000000000000000000000002
2.除數為0.0會發生什么
如果計算機是采用的IEEE 754的標准(絕大部分計算機都是采用該標准)。那么當除數為0.0時,會發生不可預期的行為(注意程序不會中斷)
#include <iostream> #include <limits> int main(){ //is_iec559是否支持IEC-559 / IEEE-754標准 std::cout << std::numeric_limits<float>::is_iec559 << std::endl; std::cout << (1.0 / 0.0) << std::endl; std::cout << (-1.0 / 0.0) << std::endl; std::cout << (0.0 / 0.0) << std::endl; return 0; }
程序的輸出結果是:
1
inf
-inf
-nan
3.浮點數的范圍
在學習過上面的知識后,我們清楚了IEEE 754中浮點數在內存中的表示形式,我們也知道0(zero)是最小的(這里和下面只討論非負數),次正規數(Denormalized Number)的表示范圍比0大,正規數(normalized Number)表示的范圍比次正規數大。
下面清楚的顯示了一些范圍和數值:
0 00000000 000000000000000000000012 = 0000 000116 = 0.12 × 2-22 × 2-126 = 2−126 × 2−23 = 2−149 ≈ 1.4012984643× 10−45
(最小的次正規數,smallest positive subnormal number)
0 00000000 111111111111111111111112 = 007f ffff16 = 0.111111111111111111111112 * 2-126 = 2−126 × (1 − 2−23) ≈ 1.1754942107 ×10−38
(最大的次正規數,largest subnormal number)
0 00000001 000000000000000000000002 = 0080 000016 = 1.02 × 21-127 = 2−126 ≈ 1.1754943508 × 10−38
(最小的正正規數,smallest positive normal number)
0 11111110 111111111111111111111112 = 7f7f ffff16 = 1.111111111111111111111112 × 2254-127 = 2127 × (2− 2−23) ≈ 3.4028234664 × 1038
(最大的正正規數,largest normal number)
0 01111110 111111111111111111111112 = 3f7f ffff16 = 1.111111111111111111111112 × 2126-127 = 1 − 2−24 ≈ 0.9999999404
(比數值1小的最大數,largest number less than one)
0 01111111 000000000000000000000002 = 3f80 000016 = 1.02 × 2127-127 = 1.02 × 20 = 1
(數值1,one)
0 01111111 000000000000000000000012 = 3f80 000116 = 1.000000000000000000000012 × 2127-127 = 1 + 2−23 ≈ 1.0000001192
(比數值1大的最小數,smallest number larger than one)
1 10000000 000000000000000000000002 = c000 000016 = −2
0 00000000 000000000000000000000002 = 0000 000016 = 0
1 00000000 000000000000000000000002 = 8000 000016 = −0
0 11111111 000000000000000000000002 = 7f80 000016 = infinity(正無窮)
1 11111111 000000000000000000000002 = ff80 000016 = −infinity(負無窮)
0 10000000 100100100001111110110112 = 4049 0fdb16 ≈ 3.14159274101 ≈ π ( 圓周率,pi )
0 01111101 010101010101010101010112 = 3eaa aaab16 ≈ 0.333333343267 ≈ 1/3
x 11111111 100000000000000000000012 = ffc0 000116 = qNaN (on x86 and ARM processors)
x 11111111 000000000000000000000012 = ff80 000116 = sNaN (on x86 and ARM processors)
通常我們所說的浮點數的范圍,都是指的正規數的存儲范圍。
| Level | Width | Range at full precision |
| Single precision | 32bits | ±1.18×10−38 to ±3.4×1038 |
| Double precision | 64 bits | ±2.23×10−308 to ±1.80×10308 |
4.浮點數的精度
在單精度浮點數中的二進制小數位有23個,所能表示2^23個數,那么只需要換算成在10進制下能夠表示相同個數的位數,就可以得到精度了。
10n = 223
10n = 8388608
106 < 8388608 < 107
所以但精度浮點數的精度為6位,同理也可以得到雙精度浮點數的精度為15位。
注意:精度為6位,並不是表示所有小於6的數都可以被精確存儲,比如0.9。因為這個精度是由二進制的精度位數計算而來的。
所以浮點數的相等判斷中,只需要判斷他們的差值小於精度就可以了。
#include <stdio.h> /* printf */ #include <math.h> /* fabs */ int main () { float f1 = 0.007; float f2 = 0.009; int res = ( fabs(f1-f2) < 1e-6 ); printf ("f1 == f2 is : %s\n",res?"true":"false"); return 0; }
輸出結果:
f1 == f2 is : false
5.參考文獻
Single-precision floating-point format_Wikipedia
IEEE 754-1985_Wikipedia
What is a subnormal floating point number?
What is a “bias value” of floating-point numbers?
