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規定了多種表示浮點數值的方式,下面介紹32位二進制的float浮點類型。它被分為3個部分,分別是符號位S(sign bit)、指數偏差E(exponent bias)和小數部分F(fraction),這三部分都是對應二進制碼的。

 

  浮點表示的一般形式為(科學技術法規則):R=(S) * (1 + F) * 2e (R:實數       S:正負符號      F:小數部分     e:指數,不同於指數偏差)。

  • 符號位S:占1位,0代表浮點數是正數,1代表浮點數是負數。
  • 指數偏差E:占8位,范圍是0~255,e = E - 127,e為正值表明轉換成二進制碼后,按科學計數法表達時向左移動了e位, 負值表明向右移動了e位。
  • 小數部分F:占23位,實際上是將浮點數轉換成二進制碼,再按科學計數法表達,將其小數部分存在F上,由於二進制碼按科學計數法表達后,只要值不為0,整數部分就必然為1,所以可以省略整數部分。

  例如,3.75的二進制碼為11.11,將該二進制碼按科學計數法表達為1.111,則向左移動了1位,即e=1,E=e+127=128,F記錄的便是小數部分,實際為111000...000。


  下面介紹一下小數部分轉換為二進制碼的方式。類似於整數的形式(如7 = 22 + 21 + 20),小數部分的轉換形式為2-1、2-2、2-3、2-4......,例如0.5 = 2-1,即二進制碼為0.1,0.05 = 2-5 + 2-6 + 2-9 + 2-10 + 2-13 + 2-14 +...... (可無限循環),即二進制碼為0.00001100110011......。如果都以16位計,那么7的二進制碼為0000000000000111,0.5的二進制碼為0.1000000000000000,0.05的二進制碼為0.0000110011001100。這是如何換算出來的呢?且看下面的算法便知:

  換算0.5,乘法結果初始為0.5,所有乘數為2,每次用乘法結果 * 乘數,得到新的乘法結果,結果中的整數部分被提取出來,剩余的小數部分繼續參加下一次乘法運算,直到剩余小數部分為0,或者無終點(無限循環)。根據表格中的整數部分可知,二進制為0.1。

整數部分 乘數 乘法結果 剩余小數部分
0. 2 0.5  0.5
1   1 0
     結束  

  換算0.05,乘法結果初始為0.05,所有乘數為2,每次用乘法結果 * 乘數,得到新的乘法結果,結果中的整數部分被提取出來,剩余的小數部分繼續參加下一次乘法運算,直到剩余小數部分為0,或者無終點(無限循環)。根據表格中的整數部分可知,二進制為0.00001100110011......。

整數部分 乘數 乘法結果 剩余小數部分
0. 2 0.05  0.05
0 2 0.1 0.1
0 2 0.2 0.2 
0 2 0.4 0.4
0 2 0.8  0.8
1 2 1.6 0.6
1 2 1.2 0.2
0 2 0.4 0.4
0 2 0.8 0.8
1  2 1.6 0.6
1   1.2 0.2
    無限循環  

例1:float型浮點數125.5轉化成32位二進制浮點數。

  125.5的整數和小數部分的二進制碼分別為1111101和0.1,於是125.5的二進制碼為1111101.1,按科學技術法寫為1.1111011*26,即向左移6位,則e=6,E=e+127=133,133的二進制碼為10000101。而1.1111011把整數部分的1去掉后,剩下小數部分為1111011,之后補0至23位,構成F。所以125.5的32位二進制浮點數為:
0  10000101  11110110000000000000000

例2:float型浮點數0.5轉化成32位二進制浮點數。

  類似的,0.5的二進制碼為0.1,按科學技術法寫為1.0*2-1,即向右移1位,則e=-1,則E=e+127=126,126的二進制碼為01111110。而1.0把整數部分的1去掉后,剩下小數部分為0,之后補0至23位,構成F。所以0.5的32位二進制浮點數為:
0  01111110  00000000000000000000000


幾個特殊的情形

  • E=0,F=0時,表示浮點數0,此時浮點數受S影響,表現出+0和-0兩種0,但數值是相等的。比如二進制數0x00000000表示+0,二進制數0x80000000表示-0。
  • E=0,F不等於0時,浮點數為(S) * (F) * 2e,注意e為-126,而不是0-127=-127,而且F是0.xxx格式而不是1.xxx格式,比如0x00000001的浮點數為2-126*2-23=1.4012984643248170709237295832899e-45,而不是20-127*(1+2-23)。E從0變為1,不會產生增加2倍的關系,而是計算公式改變了(恢復正常公式)。
  • E=255,F不等於0時,表示非數值,也就是說是非法數,例如0x7F800001。
  • E=255,F=0時,表示無窮大的數,此時浮點數受S影響,例如0x7F800000表示正無窮大,0xFF800000表示負無窮大。做除法時,如果除以0時,結果將被記作0x7F800000。

浮點數的精度

  從前文中可以看到,1.xxx這類浮點數中,F部分最小的是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

  這里只需要關注F,上面列出了1.xxx這類浮點數中的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位。因此float型的有效位數是6~7位,但這個說法應該不是非常准確,准確來說應該是6位,C語言的頭文件中規定也是6位。對於一個很大的數,例如1234567890,它是由於指數E系數而被放大了的,但它的有效位仍然是F所能表示的6~7位有效數字。1234567890用float表示后為1234567936,只有高7位是有效位,后3位是無效的。int型可以准確的表示1234567890,而float浮點數則只能近似的表示1234567890,精度問題決定了float型無法取代int型。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM