C语言的本质(4)——浮点数的本质与运算
C語言的本質(4)——浮點數的本質與運算
?
C語言規定了3種浮點數,float型、double型和long double型,其中float型占4個字節,double型占8個字節,longdouble型長度要大于等于double型,本文檔將以float型為例進行介紹,double型和long double型只是比float型位數長,原理都是一樣的。
?
float型可以表示的范圍是-3.402823466e38~3.402823466e38,而作為同為4個字節的定點數卻只能表示-2147483648~2147483647的范圍,使用同樣的內存空間,浮點數卻能比定點數表示大得多的范圍,這是不是太神奇了?既然浮點數能表示這么大的范圍,那么我們為何不使用浮點數來代替定點數呢?
?
先不說浮點數實現起來比較復雜,有些處理器還專門配置了硬件浮點運算單元用于浮點運算,主要原因是浮點數根本就無法取代定點數,因為精度問題。魚和熊掌不可兼得,浮點數表示了非常大的范圍,但它失去了非常準的精度。在說明精度問題前,我們先了解一下浮點數的格式。
?
ANSI/IEEEStd 754-1985標準
IEEE 754是最廣泛使用的二進制浮點數算術標準,被許多CPU與浮點運算器所采用。IEEE754規定了多種表示浮點數值的方式,在本文檔里只介紹32bits的float浮點類型。它被分為3個部分,分別是符號位S(sign bit)、指數偏差E(exponent bias)和小數部分F(fraction)。
其中S位占1bit,為bit31。S位為0代表浮點數是正數,S位為1代表浮點數是負數,比如說0x449A522C的S位為0,表示這是一個正數,0x849A522C的S位為1,表示這是一個負數。
E位占8bits,為bit23~bit30。E位代表2的N次方,但需要減去127,比如說E位為87,那么E位的值為2(87-127)=9.094947017729282379150390625e-13。
F位占23bits,為bit0~bit22。F位是小數點后面的位數,其中bit22是2-1=0.5,bit21是2-2=0.25,以此類推,bit0為2-23=0.00000011920928955078125。但F位里隱藏了一個1,也就是說F位所表示的值是1+(F位bit22~bit0所表示的數值),比如說F位是0b10100000000000000000001,只有bit22、bit20和bit0為1,那么F位的值為1+(2-1+2-3+2-23),為1.62500011920928955078125。
?
?綜上所述,從二進制數換算到浮點數的公式為:(-1)S×2E-127×(1+F)。但還有幾個特殊的情形:
?
1、若E位為0并且F位也為0時表示浮點數0,此時浮點數受S位影響,表現出+0和-0兩種0,但數值是相等的。比如二進制數0x00000000表示+0,二進制數0x80000000表示-0。
2、若E位為0并且F位不為0時浮點數為(-1)S×2-126×F,注意,E位的指數是-126,而不是0-127=-127,而且F位是0.xx格式而不是1.xx格式,比如0x00000001的浮點數為2-126×2-23=1.4012984643248170709237295832899e-45,而不是20-121×(1+2-23)。一旦E為不為0,從0變為1,不是增加2倍的關系,因為公式改變了。
3、若E位為255并且F位不為0時表示非數值,也就是說是非法數,例如0x7F800001。
4、 若E位為255并且F位為0時表示無窮大的數,此時浮點數受S位影響,例如0x7F800000表示正無窮大,0xFF800000表示負無窮大。當我們使用1個數除以0時,結果將被記作0x7F800000。
???????
浮點型在多個處理器間通信時,傳遞的數值是它的二進制數,比如說1234.5678這個浮點數的二進制數是0x449A522B,如果使用串口發送的話,就會發現串口里發送的是0x44、0x9A、0x52和0x2B這4個數(發送的順序也可能是逆序,這與約定的字節序有關,與浮點格式無關),接收端接收到這4個數字后再組合成0x449A522B,按照IEEE 754的定義被解析成1234.5678,這樣就實現浮點數通信了。如果兩個處理器所使用的浮點數規則不同,則無法傳遞浮點數。
???????
浮點數的換算
下面來看看浮點數與二進制數如何轉換。
1、二進制數換算成浮點數:
?
假如在內存中有一個二進制數為0x449A522C,先將十六進制轉換成二進制,如下:
0100?0100? 1001? 1010?0101? 0010? 0010?1100
?
按照SEF的格式分段,如下:
0?10001001? 00110100101001000101100
?
這個數值不是特殊的情形,可以按照公式(-1)S×2E-127×(1+F)轉換。S位的值為(-1)0=1,E位的值為2137-127=1024。F位的值為1+2-3+2-4+2-6+2-9+2-11+2-14+2-18+2-20+2-21= 1.205632686614990234375。最終結果為1×1024×1.205632686614990234375=1234.56787109375。
?
其中F位比較長,使用二進制方式轉換比較麻煩,也可以先轉換成十六進制再計算,轉換為十六進制如下:
0011?0100? 1010? 0100?0101? 1000
0x3??0x4?? 0xA?? 0x4??0x5?? 0x8
?
F位為23bits,需要在最后補一個0湊成24bits,共6個十六進制數。F位的值為1+3×16-1+4×16-2+10×16-3+4×16-4+5×16-5+8×16-6=1.205632686614990234375,與上面使用二進制方法得到的結果相同。
?
2、浮點數換算成二進制數:
?
下面我們將-987.654e30換算成二進制數。我們先不看符號位,將987.654e30歸一化為整數部分為1的形式,也就是寫作987.654e30=2E-127×(1+F)的標準形式,其中E=log(987.654e30)/log2+127=109.6+127,取E位的整數值為109+127=236,再求F=987.654e30/2236-127-1=0.52172193,這個小數位數保留8位就夠了,已經超出了7位的精度。然后我們求小數部分的二進制數,這個轉換就沒啥好說的了,依次減去2的冪,從2-1一直到2-23,夠減的位置1,不夠減的位置0,例如,2-1為0.5,0.52172193-0.5=0.02172193,F位的bit22置1,2-2為0.25,0.02172193不夠減,F位的bit21置0,2-3為0.125,0.02172193不夠減,F位的bit20置0,2-4為0.0625,0.02172193不夠減,F位的bit19置0……,一直算到F位的bit0,這樣就得到F位的數值。
?
如果覺得使用二進制方式轉換太麻煩的話也可以使用十六進制進行轉換。16-1為0.0625,0.52172193/0.0625=8.3,說明夠減8個,記做0x8,0.52172193-0.0625×8=0.02172193,16-2為0.00390625,0.02172193/0.00390625=5.6,說明夠減5個,加上剛才的0x8記做0x85,以此類推:
? | 16的-N次冪 | 被減數 | 十六進制數 | 減后的數 |
1 | 0.0625 | 0.52172193 | 0x8 | 0.02172193 |
2 | 0.00390625 | 0.02172193 | 0x85 | 0.00219068 |
3 | 0.000244140625 | 0.00219068 | 0x858 | 0.000237555 |
4 | 0.0000152587890625 | 0.000237555 | 0x858F | 0.0000086731640625 |
5 | 0.00000095367431640625 | 0.0000086731640625 | 0x858F9 | 0.00000009009521484375 |
6 | 0.000000059604644775390625 | 0.00000009009521484375 | 0x858F91 | ? |
?
一直湊夠23bits,也就是6個十六進制,得到0x858F91,換算成二進制如下所示:
1000?0101? 1000? 1111?1001? 0001
?
由于只有23bits有效,因此需要去掉最后一個bit,二進制本著0舍1入的原則,變成
1000?0101? 1000? 1111?1001? 001
?
最后需要再補上前面的S位和E位。由于是負數,S位為1。E位為236,二進制形式為1110 1100,將S、E、F位組合在一起就形成了:
1?1110 1100? 1000? 0101?1000? 1111? 1001?001
?
從左邊最高位開始,4個一組合并成十六進制:
1111?0110? 0100? 0010?1100? 0111? 1100?1001
?
換算成十六進制為:
0xF??0x6?? 0x4?? 0x2??0xC?? 0x7? 0xC??0x9
所以-987.654e30換算成二進制數為0xF642C7C9。
???????
浮點數的精度
?
在前面的講解中可以看到1.xx這個數量級的最小數是2-23,對應的十進制數值為1.00000011920928955078125,可以精確表示到小數點后23位,但有些C語言書上卻說float型的有效位只有6~7位,這是為什么?
?
這是因為二進制小數與十進制小數沒有完全一一對應的關系,二進制小數對于十進制小數來說相當于是離散的而不是連續的,我們來看看下面這些數字:
二進制小數??????? 十進制小數
2-23???????1.00000011920928955078125
2-22???????1.0000002384185791015625
2-21???????1.000000476837158203125
2-20???????1.00000095367431640625
2-19???????1.0000019073486328125
2-18???????1.000003814697265625
?
不看S位和E位,只看F位,上表列出了1.xx這個數量級的6個最小冪的二進制小數,對應的十進制在上表的右邊,可以看到使用二進制所能表示的最小小數是1.00000011920928955078125,接下來是1.0000002384185791015625,這兩個數之間是有間隔的,如果想用二進制小數來表示8位有效數(只算小數部分,小數點前面的1是隱藏的默認值)1.00000002、1.00000003、1.00000004...這些數是無法辦到的,而7位有效數1.0000001可以用2-23來表示,1.0000002可以用2-22來表示,1.0000003可以用2-23+2-22來表示。從這個角度來看,float型所能精確表示的位數只有7位,7位之后的數雖然也是精確表示的,但卻無法表示任意一個想表示的數值。
?
但還是有一些例外的,比如說7位有效數1.0000006這個數就無法使用F位表示,二進制小數對于十進制小數來說相當于是離散的,剛好湊不出1.0000006這個數,從這點來看float型所能精確表示的位數只有6位。至于5位有效值的任何數都是可以使用F位相加組合出來的,即便是乘以E位的指數后也是可以準確表示出來的。
?
因此float型的有效位數是6~7位,但這個說法應該不是非常準確,準確來說應該是6位,C語言的頭文件中規定也是6位。
?
對于一個很大的數,比如說1234567890,它是F位乘上E位的系數被放大了的,但它的有效位仍然是F位所能表示的6位有效數字。1234567890對應的二進制數是0x4E932C06,其中F位的數值為1.1497809886932373046875,E位的數值為230=1073741824,1073741824×1.1497809886932373046875=1234567936,對比1234567890,也只有高7位是有效位,后3位是無效的。int型定點數可以準確的表示1234567890,而float浮點數則只能近似的表示1234567890,精度問題決定了float型根本無法取代int型。
???????
浮點數的比較
?
float型的有效位數是6位,那么我們在用float型運算時就要注意了,來看下面這段程序:
#include <stdio.h>int main(void) {float a = 9.87654321;float b = 9.87654322;if(a > b){printf("a > b\n");}else if(a == b){printf("a == b\n");}else{printf("a < b\n");}return 0; }?
按照我們平時的經驗來說這段程序應該走a < b的分支,但程序運行的結果卻走了a == b的分支,原因就是float型的精度問題,float型無法區分出小數點后的第8位數,在內存中,a和b的二進制數都是0x411E0652,因此就走了a == b的分支。
?
某些編譯器在編譯時會發現a和b的值超出了浮點數的精度,會產生一個告警,提示數據超過精度,但有些編譯器則不會做任何提示。最可怕的是有一類編譯器,調試窗口里顯示的長度超出float型的精度,比如說a的值顯示為9.87654321,b的值顯示為9.87654322,但在運行時,硬件可不管這套,硬件認為這2個數都是0x411E0652,因此實際運行結果是a == b的分支。
?
由于精度這個問題的限制,我們在浮點數比較時就需要加一個可接受的精度條件來做判決,比如說上面的這個問題,如果我們認為精度在0.00001就足夠了,那么a - b之差的絕對值只要小于0.00001,我們就認為a和b的值是相等的,大于0.00001則認為不等,還要考慮到a - b正負等情況,因此可以將上面的程序改寫為:
?
#include <stdio.h>int main(void) {float a = 9.87654321;float b = 9.87654322;if(a - b < -0.00001){printf("a < b\n");}else if(a - b > 0.00001){printf("a > b\n");}else{printf("a == b\n");}return 0; }?
例子中a和b之差的絕對值小于0.00001,因此認為a和b是相等的,運行程序,也正確的打印了a == b。
??
為什么我們要做一個精度的限定?這是因為我們在應用中的精度往往要低于硬件的6位精度。
?
浮點數的加減
?二進制小數與十進制小數之間不存在一一對應的關系,因此某些十進制很整的加減法小數運算由二進制小數來實現就表現出了不整的情況,來看下面的例子:
?
#include <stdio.h>int main(void) {float a = 10.2;float b = 9;float c;c= a - b;printf("%f\n", c);return 0; }?
如果用十進制計算的話變量c應該為1.2,在Visual C++ 2010環境下實驗輸出為1.200000,但實際上c變量的值是1.1999998,只不過是在輸出時被四舍五入為1.200000罷了。在內存中c變量的二進制數是0x3F999998,它對應的浮點數是0.19999980926513671875。如果我們將printf函數%f的格式改為%.7f格式,就會看到c變量輸出的值是1.1999998。
?
兩個數量級相差很大的數做加減運算時,數值小的浮點數會受精度限制而被忽略,看下面的例子:
#include <stdio.h>int main(void) {float a = 987654321;float b = 987.654322;float c;c= a + b;printf("%f\n", c);return 0; }?
Visual C++ 2013上的計算結果為987655296.000000,而實際的真實值為987655308.654322,二進制值為0x4E6B79B2對應987655296,就是987655296.000000。可以看出有效值是6位,如果按四舍五入的話可以精確到8位,其中變量b貢獻的有效數值只有2位。
? ? ? 987654321
+????987.654322
--------------------------------
987655308.654322??? 真實值
987655296.000000??? 計算值
987655296?????????? 二進制值,0x4E6B79B2
?
對于這種數量級相差很大的計算,計算結果會保證高位數有效,數量級小的數相對計算結果顯的太小了,不能按自身6位的精度保持,而是需要按照計算結果的6位精度保持。
?
C語言中有關浮點數的定義
C語言對浮點數做了一些規定,下面是摘自VisualC++ 2013頭文件float.h中有關float型的定義,如下:
#define FLT_DIG 6 /* # of decimal digitsof precision */ #define FLT_EPSILON 1.192092896e-07F /* smallest such that 1.0+FLT_EPSILON!= 1.0 */ #define FLT_GUARD 0 #define FLT_MANT_DIG 24 /* # of bits in mantissa*/ #define FLT_MAX 3.402823466e+38F /* max value */ #define FLT_MAX_10_EXP 38 /* max decimal exponent*/ #define FLT_MAX_EXP 128 /* max binary exponent */ #define FLT_MIN 1.175494351e-38F /* min positive value */ #define FLT_MIN_10_EXP (-37) /* min decimal exponent */ #define FLT_MIN_EXP (-125) /* min binary exponent */其中FLT_DIG定義了float型的十進制精度,是6位,與我們上面的討論是一致的。
FLT_EPSILON定義了float型在1.xx數量級下的最小精度,1.xx數量級下判斷浮點數是否為0可以使用這個精度。
FLT_MANT_DIG定義了float型F位的長度。
FLT_MAX定義了float型可表示的最大數值。
FLT_MAX_10_EXP定義了float型十進制的最大冪。
FLT_MAX_EXP定義了float型二進制的最大冪。
FLT_MIN定義了float型所能表示的最小正數。
FLT_MIN_10_EXP定義了float型十進制的最小冪。
FLT_MIN_EXP定義了float型二進制的最小冪。
?
float.h文件里對其它的浮點數也做了規定,有興趣的讀者可以打開float.h頭文件繼續研究。
轉載于:https://www.cnblogs.com/new0801/p/6177121.html
總結
以上是生活随笔為你收集整理的C语言的本质(4)——浮点数的本质与运算的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: insertion Sort List
- 下一篇: Windows Server2008下M