基本數據類型操作一:C語言浮點數解惑

前言

        有些C語言書上說float型的有效位數是6~7位,爲什麼不是6位或者7位?而是一個變化的6~7位?
        浮點數在內存中是如何存放的?
        float浮點數要比同爲4字節的int定點數表示的範圍大的多,那麼是否可以使用浮點數替代定點數?
        爲什麼float型浮點數9.87654321 > 9.87654322不成立?爲何10.2 - 9的結果不是1.2,而是1.1999998?爲何987654321 + 987.654322的結果不是987655308.654322?
        如何才能精確比較浮點數真實的大小?
        看完本文檔,你將會得到答案!
        聲明:本文來自:http://bbs.chinaunix.net/thread-3746530-1-1.html,看後很受用,整理轉載。
        本文的pdf文檔:http://download.csdn.net/detail/chenyiming_1990/5916393,免費下載學習使用。

C語言浮點數

        C語言標準C89裏規定了3種浮點數,float型、double型和long double型,其中float型佔4個字節,double型佔8個字節,long double型長度要大於等於double型,本文檔將以float型爲例進行介紹,double型和long double型只是比float型位數長,原理都是一樣的。
        float型可以表示的範圍是-3.402823466e38~3.402823466e38,而作爲同爲4個字節的定點數卻只能表示-2147483648~2147483647的範圍,使用同樣的內存空間,浮點數卻能比定點數表示大得多的範圍,這是不是太神奇了?既然浮點數能表示這麼大的範圍,那麼我們爲何不使用浮點數來代替定點數呢?
        先不說浮點數實現起來比較複雜,有些處理器還專門配置了硬件浮點運算單元用於浮點運算,主要原因是浮點數根本就無法取代定點數,因爲精度問題。魚和熊掌不可兼得,浮點數表示了非常大的範圍,但它失去了非常準的精度。在說明精度問題前,我們先了解一下浮點數的格式。
       

ANSI/IEEE Std 754-1985標準

IEEE 754是最廣泛使用的二進制浮點數算術標準,被許多CPU與浮點運算器所採用。IEEE 754規定了多種表示浮點數值的方式,在本文檔裏只介紹32bits的float浮點類型。它被分爲3個部分,分別是符號位S(sign bit)、指數偏差E(exponent bias)和小數部分F(fraction)。
浮點數.JPG


特殊的情形

        其中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,以此類推:

   

        一直湊夠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位,這是爲什麼?
        這是因爲二進制小數與十進制小數沒有完全一一對應的關係,二進制小數對於十進制小數來說相當於是離散的而不是連續的,我們來看看下面這些數字:
        不看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型運算時就要注意了,來看下面這段程序:
  1. #include <stdio.h>   
  2.   
  3. int main(void)  
  4. {  
  5.     float a = 9.87654321;  
  6.     float b = 9.87654322;  
  7.       
  8.     if(a > b)  
  9.     {  
  10.         printf("a > b\n");  
  11.     }  
  12.     else if(a == b)  
  13.     {  
  14.         printf("a == b\n");  
  15.     }  
  16.     else  
  17.     {  
  18.         printf("a < b\n");  
  19.         }  
  20.   
  21.     return 0;  
  22. }  
#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的分支。以前就遇到過一個同學在QQ羣裏問一個類似的問題,在調試窗口裏明明寫着a是9.87654321,小於 b的9.87654322,但運行結果卻是a ==b。當時我給他說了半天也沒能讓他明白這個問題,希望他能有機會看到這個文檔,希望他這次能夠明白^_^。
       
        由於精度這個問題的限制,我們在浮點數比較時就需要加一個可接受的精度條件來做判決,比如說上面的這個問題,如果我們認爲精度在0.00001就足夠了,那麼a - b之差的絕對值只要小於0.00001,我們就認爲a和b的值是相等的,大於0.00001則認爲不等,還要考慮到a - b正負等情況,因此可以將上面的程序改寫爲:
  1. #include <stdio.h>   
  2.   
  3. int main(void)  
  4. {  
  5.     float a = 9.87654321;  
  6.     float b = 9.87654322;  
  7.   
  8.     if(a - b < -0.00001)  
  9.     {  
  10.         printf("a < b\n");  
  11.     }  
  12.     else if(a - b > 0.00001)  
  13.     {  
  14.         printf("a > b\n");  
  15.     }  
  16.     else  
  17.     {  
  18.         printf("a == b\n");  
  19.         }  
  20.   
  21.     return 0;  
  22. }  
#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。

        也許你會覺得費了這麼大的勁,最後2個程序運行的結果還是一樣的,這不是畫蛇添足麼?硬件已經自動考慮到精度問題了,爲什麼我們還要去再做一個精度的限定?這是因爲我們在應用中的精度往往要低於硬件的6位精度。比如說我們使用2個AD採集2V的電壓,並對這2個電壓值做比較,一般要求精確到0.1V即可。實際情況下AD採集出來的數值都會圍繞真實值有上下的波動,比如說AD的精度是0.001V,我們採集出的電壓數值就可能是2.003V、2.001V、1.999V等圍繞2V波動的數值,如果我們在程序裏不對精度加以限制就對這些數值做比較,就會發現幾乎每次採集到的電壓值都是不同的。在這種情況下我們就需要將精度設定爲0.1V,將上面例子中的0.00001改爲0.1,就可以正確的判斷出每次採集的電壓值都是相同的了。
        在實際使用AD採樣時可能並不需要使用浮點數,我一般都是使用定點數來代替浮點數進行處理的,請參考另一篇案例《C語言使用定點數代替浮點數計算》。

        下面我們再看一個例子:
  1. #include <stdio.h>   
  2.   
  3. int main(void)  
  4. {  
  5.     float a = 987654321;  
  6.     float b = 987654322;  
  7.   
  8.     if(a - b < -0.00001)  
  9.     {  
  10.         printf("a < b\n");  
  11.     }  
  12.     else if(a - b > 0.00001)  
  13.     {  
  14.         printf("a > b\n");  
  15.     }  
  16.     else  
  17.     {  
  18.         printf("a == b\n");  
  19.         }  
  20.   
  21.     return 0;  
  22. }  
#include <stdio.h>

int main(void)
{
    float a = 987654321;
    float b = 987654322;

    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;
}
        這個例子中的兩個數都是很大的數,已經遠遠超過了0.00001的精度,運行結果是不是應該是a < b?但程序運行的結果依然是a == b。這是因爲這個例子裏的a和b並不是1.xx的數量級,我們將a和b進行歸一化,都除以900000000就會發現a = 1.09739369,b = 1.097393691,只是在第9位纔出現不同,因此在0.00001這個精度下,這2個數還是相等的。換個角度來看,a和b雖然是很大的數了,但F位僅能表示23bits,有效值僅有6位,a和b的大是因爲E位的指數放大F位表現出來的,但有效值依然是6位。在內存中a和b的二進制數都是0x4E6B79A3。其中E位爲156,2156-127=536870912,F位爲0xD6F346,F位1.xx數量級的1.83964955806732177734375被E位放大了536870912倍,E位如果算7位有效精度的話能精確到0.0000001,乘以536870912已經被放大了53倍多,這就說明a和b的個位與十位已經不是有效位數了,所以程序運行的結果表現出a == b也是正常的。
        由此可見,設定一個合理的精度是需要結合浮點數的數量級的,這看起來似乎比較難,畢竟在程序運行時十分精確的跟蹤每個浮點數的數量級並不容易實現,但實際應用中需要比較的浮點數往往都會有其物理含義,例如上面電壓的例子,因此,根據浮點數的物理含義,浮點數的精度還是比較好確定的。當然在一些複雜的數值運算過程中可能會存在非常複雜的情況,這時浮點數的精度問題就比較棘手了,所幸我所做的都是比較簡單的東西,那些複雜的情況就不討論了,我也沒能力討論^_^。
        上面所說的都是同等數量級下的浮點數進行比較的情況,不同數量級的浮點數比較則沒有這個限制,比如說1.23456789與12.23456789的比較,在E位已經足以區分大小了,因此F位的精度就沒有必要再比較了。
       
浮點數的加減
        二進制小數與十進制小數之間不存在一一對應的關係,因此某些十進制很整的加減法小數運算由二進制小數來實現就表現出了不整的情況,來看下面的例子:
  1. #include <stdio.h>   
  2.   
  3. int main(void)  
  4. {  
  5.     float a = 10.2;  
  6.     float b = 9;  
  7.     float c;  
  8.   
  9.     c = a - b;  
  10.   
  11.         printf("%f\n", c);  
  12.   
  13.     return 0;  
  14. }  
#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。
       
        兩個數量級相差很大的數做加減運算時,數值小的浮點數會受精度限制而被忽略,看下面的例子:
  1. #include <stdio.h>   
  2.   
  3. int main(void)  
  4. {  
  5.     float a = 987654321;  
  6.     float b = 987.654322;  
  7.     float c;  
  8.   
  9.     c = a + b;  
  10.   
  11.     printf("%f\n", c);  
  12.   
  13.     return 0;  
  14. }  
#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++ 2010上的計算結果爲987655296.000000,而實際的真實值爲987655308.654322,二進制值爲0x4E6B79B2對應987655296,就是987655296.000000。可以看出有效值是6位,如果按四捨五入的話可以精確到8位,其中變量b貢獻的有效數值只有2位。
987654321
+     987.654322
----------------
987655308.654322    真實值
987655296.000000    計算值
987655296           二進制值,0x4E6B79B2
        對於這種數量級相差很大的計算,計算結果會保證高位數有效,數量級小的數相對計算結果顯的太小了,不能按自身6位的精度保持,而是需要按照計算結果的6位精度保持。

使用二進制數比較浮點數

        下面我們從另一個方向探索一下浮點數的比較問題。
        我們可以使用(-1)S×2E-127×(1+F)這個公式來計算IEEE 754標準規定的浮點數,先拋開S位和那4種特殊的規定,只看E位和F位2E-127×(1+F),我們會發現E位和F位組成的數值具有單調遞增性,也就是說任意一個浮點數A掩掉S位的數值B = (A & 0x7FFFFFFF)是單調遞增的,如果A1大於A2,那麼B1一定大於B2,反之亦然,如果B1大於B2,那麼A1也一定大於A2,這樣的話我們就可以使用浮點數的二進制數來比較大小了。
        看下面程序,使用聯合體將浮點數轉換成二進制數再做比較:
  1. #include <stdio.h>   
  2.   
  3. typedef union float_int  
  4. {  
  5.     float f;  
  6.     int i;  
  7. }FLOAT_INT;  
  8.   
  9. int main(void)  
  10. {  
  11.     FLOAT_INT a;  
  12.     FLOAT_INT b;  
  13.     int ca;  
  14.     int cb;  
  15.   
  16.     a.f = 9.87654321;  
  17.     b.f = 9.87654322;  
  18.   
  19.     ca = a.i & 0x7FFFFFFF;  
  20.     cb = b.i & 0x7FFFFFFF;  
  21.   
  22.     if(ca > cb)  
  23.     {  
  24.         printf("a > b\n");  
  25.     }  
  26.     else if(ca == cb)  
  27.     {  
  28.         printf("a == b\n");  
  29.     }  
  30.     else  
  31.     {  
  32.         printf("a < b\n");  
  33.     }  
  34.   
  35.     return 0;  
  36. }  
#include <stdio.h>

typedef union float_int
{
    float f;
    int i;
}FLOAT_INT;

int main(void)
{
    FLOAT_INT a;
    FLOAT_INT b;
    int ca;
    int cb;

    a.f = 9.87654321;
    b.f = 9.87654322;

    ca = a.i & 0x7FFFFFFF;
    cb = b.i & 0x7FFFFFFF;

    if(ca > cb)
    {
        printf("a > b\n");
    }
    else if(ca == cb)
    {
        printf("a == b\n");
    }
    else
    {
        printf("a < b\n");
    }

    return 0;
}
        上面的程序使用聯合體使浮點型和整型共享同一個內存空間,浮點型變量.f輸入浮點數,使用整型變量.i就可以獲取到.f的二進制數,比較時利用.i的E位和F位就可以判斷浮點數的絕對大小了,這個判決的精度爲硬件所支持的精度。
       
        如果考慮到S位,情況會有些變化。S位是符號位,0正1負,與int型的符號位有一樣的作用,並且都在bit31。從這點來看,不對浮點數的二進制數進行(& 0x7FFFFFFF)的操作而是直接使用浮點數的二進制數來當做int型數做比較,那麼浮點數的S位則正好可以充當int型數的符號位。兩個比較的浮點數都是正數的情況就不用說了,上面的例子(& 0x7FFFFFFF)已經驗證了。正浮點數與負浮點數比較的情況也沒有問題,浮點數和int型數的符號位是兼容的,符號位就可以直接比較出大小,比如說-9.87654321和9.87654322之間做比較,-9.87654321的bit31是1,9.87654322的bit31是0,從二進制int型數的角度來看,bit31爲0是正數,bit31爲1是負數,通過符號位就可以直接判斷出大小。最後剩下兩個負浮點數比較的情況了,這種情況存在問題,如果採用二進制int型數來比較浮點數的話,結果則正好相反,比如說-1.5和-1.25做比較,int型數是用補碼錶示的,對於兩個負數來說,補碼的二進制數值越大則補碼值也越大。-1.5的補碼是0xBFC00000,-1.25的補碼是0xBFA00000,從二進制角度來看0xBFC00000>0xBFA00000,因此int的補碼是0xBFC00000>0xBFA00000,也就是-1077936128>-1080033280,如果使用int型來判斷,就會得出-1.5 > -1.25的結論,正好相反了。這樣的話我們就需要對兩個浮點數的符號位做一個判斷,如果同爲負數的話則需要將比較結果反一下,如下面程序:
  1. #include <stdio.h>   
  2.   
  3. typedef union float_int  
  4. {  
  5.     float f;  
  6.     int i;  
  7. }FLOAT_INT;  
  8.   
  9. int main(void)  
  10. {  
  11.     FLOAT_INT a;  
  12.     FLOAT_INT b;  
  13.   
  14.     a.f = -9.876543;  
  15.     b.f = -9.876542;  
  16.   
  17.     if((a.i < 0) && (b.i < 0))  
  18.     {  
  19.         if(a.i < b.i)  
  20.         {  
  21.             printf("a > b\n");  
  22.         }  
  23.         else if(a.i == b.i)  
  24.         {  
  25.             printf("a == b\n");  
  26.         }  
  27.         else  
  28.         {  
  29.             printf("a < b\n");  
  30.         }  
  31.     }  
  32.     else  
  33.     {  
  34.         if(a.i > b.i)  
  35.         {  
  36.             printf("a > b\n");  
  37.         }  
  38.         else if(a.i == b.i)  
  39.         {  
  40.             printf("a == b\n");  
  41.         }  
  42.         else  
  43.         {  
  44.             printf("a < b\n");  
  45.         }  
  46.     }  
  47. }  
#include <stdio.h>

typedef union float_int
{
    float f;
    int i;
}FLOAT_INT;

int main(void)
{
    FLOAT_INT a;
    FLOAT_INT b;

    a.f = -9.876543;
    b.f = -9.876542;

    if((a.i < 0) && (b.i < 0))
    {
        if(a.i < b.i)
        {
            printf("a > b\n");
        }
        else if(a.i == b.i)
        {
            printf("a == b\n");
        }
        else
        {
            printf("a < b\n");
        }
    }
    else
    {
        if(a.i > b.i)
        {
            printf("a > b\n");
        }
        else if(a.i == b.i)
        {
            printf("a == b\n");
        }
        else
        {
            printf("a < b\n");
        }
    }
}
        如果再考慮IEEE 754標準定義的那幾種特殊情況,問題變得又會複雜一些,比如說在運算過程中有±x / 0的情況出現,那麼結果就是一個±無窮大的數,還有可能遇到±0等情況,這些問題在這裏就不討論,只要增加相應的條件分支就可以做出判斷的。
       
        使用二進制數比較浮點數的方法可以依據硬件精度判斷出浮點數的真正大小,但實際使用過程中往往不是根據硬件精度做判斷的,因此最好還是使用上面所介紹的加入精度的判斷方法。
       

C語言中有關浮點數的定義

        C語言對浮點數做了一些規定,下面是摘自Visual C++ 2010頭文件float.h中有關float型的定義,如下:
#define FLT_DIG                     6                                         /* # of decimal digits of 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_GUARD不知道是啥意思--!
        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文件裏對其它的浮點數也做了規定,本文不再做介紹了。

轉載:點擊打開鏈接

發佈了47 篇原創文章 · 獲贊 22 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章