周冠方
(鄖陽師范高等??茖W(xué)校組織人事部,湖北十堰420000)
C語言中浮點(diǎn)數(shù)精度問題分析
周冠方
(鄖陽師范高等??茖W(xué)校組織人事部,湖北十堰420000)
通過實(shí)例直觀地描述了C語言中由于計(jì)算機(jī)存儲(chǔ)數(shù)據(jù)方式的不同而造成的數(shù)據(jù)誤差,并對(duì)誤差產(chǎn)生的原因進(jìn)行了分析,解讀出C語言中浮點(diǎn)型數(shù)據(jù)的不同存儲(chǔ)方式,最后給出幾點(diǎn)建議。
C語言;數(shù)據(jù)精度;浮點(diǎn)型數(shù)據(jù);相對(duì)誤差
C語言程序設(shè)計(jì)的基本數(shù)據(jù)類型包含整型和浮點(diǎn)型兩類。在計(jì)算機(jī)中,實(shí)數(shù)特別是小數(shù)形式表示的數(shù)都是以浮點(diǎn)型數(shù)據(jù)來進(jìn)行存儲(chǔ)的。但是對(duì)于浮點(diǎn)型的數(shù)據(jù),在進(jìn)行各種運(yùn)算時(shí),因?yàn)橛?jì)算機(jī)的二進(jìn)制存儲(chǔ)特性,會(huì)導(dǎo)致出現(xiàn)精度丟失的現(xiàn)象。這種現(xiàn)象直接影響到程序結(jié)果的準(zhǔn)確性和可靠性。
先來看一個(gè)簡(jiǎn)單的例子:需要求解A=4/5的值,這個(gè)結(jié)果很簡(jiǎn)單,A=0.8。而在C語言中,我們寫出它的計(jì)算程序:
Main()
{
Float A;
A=4/5;
Printf(“A=%f\n”,A);
Return 0;
}
結(jié)果:A=0.000000
從算法的角度來看這個(gè)程序沒有問題,但是最終運(yùn)行的結(jié)果卻和我們的理論值完全不相符。這是為什么呢?
分析:在此程序中兩操作數(shù)4和5均為整型,運(yùn)算結(jié)果應(yīng)該為0.8,但是在C語言程序編寫中有這樣一個(gè)規(guī)則,C語言中計(jì)算的源數(shù)據(jù)為整形數(shù)據(jù),最終輸出結(jié)果也應(yīng)該為整形數(shù)據(jù)。“4/5”的結(jié)果0.8在C語言中會(huì)被轉(zhuǎn)換后得到一個(gè)int型的中間變量,它的值等于“4/5”的整數(shù)部分,其小數(shù)部分則被進(jìn)行截尾操作,即舍棄整個(gè)小數(shù)部分,最終存儲(chǔ)值為0。程序?qū)⑦@個(gè)整型的運(yùn)算結(jié)果賦給A這個(gè)float型變量,將其強(qiáng)制轉(zhuǎn)換為float型輸出,所以最終運(yùn)算結(jié)果成了A=0.000000。
如果我們需要去確保最終輸出結(jié)果的正確性,我們就必須在計(jì)算的過程中就將中間的操作數(shù)據(jù)更改為浮點(diǎn)型數(shù)據(jù),來確保最終結(jié)果數(shù)據(jù)和中間操作數(shù)據(jù)的數(shù)據(jù)類型的同質(zhì)性。
此例有兩種簡(jiǎn)單的解決辦法:
1)將“A=4/5”改成“A=4.0/5.0”;
2)將“A=4/5”改成“A=(float)4/5”。
即可得出最終正確的值A(chǔ)=0.800000。因此,如果我們遇到因?yàn)镃語言程序設(shè)計(jì)中,不同數(shù)據(jù)類型之間的計(jì)算問題時(shí),我們必須規(guī)定明確的數(shù)據(jù)類型,并且在算法編寫的過程中,通過人為的數(shù)據(jù)類型變更的方法,確保程序計(jì)算中過程值和結(jié)果值的數(shù)據(jù)類型的同質(zhì)性,從而達(dá)到保證計(jì)算精度準(zhǔn)確的目的。
同樣的例子,我們做一個(gè)簡(jiǎn)單的修改:
Main()
{
Float A;
A=(float)4/5;
Printf(“A=%10.8f\n”,A);
Return 0;
}
我們將輸出結(jié)果限定為10位有效數(shù)字,小數(shù)點(diǎn)后有效數(shù)字為8位。程序運(yùn)行后得到結(jié)果為:A=0.80000001。這顯然也不是我們想要的結(jié)果:A=0.80000000。為了知道這個(gè)原因,我們就必須了解C語言中浮點(diǎn)類型數(shù)據(jù)的存儲(chǔ)格式要求。
3.1 C語言中浮點(diǎn)類型數(shù)據(jù)的存儲(chǔ)格式
C語言中的浮點(diǎn)數(shù)是以IEEE 754標(biāo)準(zhǔn)的格式存儲(chǔ),與整型數(shù)據(jù)的存儲(chǔ)完全不一樣。
3.1.1 單精度浮點(diǎn)型數(shù)據(jù)
C語言中對(duì)float型數(shù)據(jù)(4個(gè)字節(jié))的表示分為三個(gè)部分:符號(hào)S,階碼E,尾數(shù)M。具體如下(見表1):
表1 單精度浮點(diǎn)型數(shù)據(jù)存儲(chǔ)格式表
1)最高位31位,保存符號(hào)位S,“0”表示正數(shù),“1”表示負(fù)數(shù)。[1]
2)30位~23位,共8位,移碼方式(指數(shù)值加上偏移量127)保存指數(shù)部分,稱為階碼。
3)22位~0位,共23位,保存系數(shù)部分,稱為尾數(shù),對(duì)于規(guī)范化二進(jìn)制數(shù),整數(shù)位的前導(dǎo)“1”不保存(隱含),直接保存小數(shù)部分b1b2…b23。
實(shí)際上即是將十進(jìn)制數(shù)R在計(jì)算機(jī)中用二進(jìn)制數(shù)的科學(xué)計(jì)數(shù)法表示出來:R=(-1)S×M×2E。
而在float類型的數(shù)據(jù)中,它的精度是由尾數(shù)的位數(shù)來決定的。浮點(diǎn)數(shù)在內(nèi)存中是按科學(xué)計(jì)數(shù)法來存儲(chǔ)的,其整數(shù)部分始終是一個(gè)隱含著的“1”,由于它是不變的,故不能對(duì)精度造成影響。
float:2^23=8388608,一共七位,106>8388608>107這意味著最多能有7位有效數(shù)字,但絕對(duì)能保證的為6位,即float的精度為6~7位有效數(shù)字。因此,當(dāng)我們用“A=%10.8f\n”來控制輸出結(jié)果的小數(shù)位數(shù)為8位時(shí),就會(huì)出現(xiàn)精度丟失的問題,即多出來了0.00000001。
Float型的一些特殊約定:[2]
2)當(dāng)E=0,M?。?時(shí),表示非規(guī)范化數(shù),即r=(-1)S×2-127×(0.M);
3)當(dāng)E=255,M=0時(shí),表示無窮大,用符號(hào)位來確定是正無窮大還是負(fù)無窮大;
4)當(dāng) E=255,M?。?時(shí),表示 NaN(Not a Number,不是一個(gè)數(shù));
對(duì)于Double型,也有相似的約定。
3.1.2 雙精度浮點(diǎn)型數(shù)據(jù)
在控制輸出結(jié)果的實(shí)際位數(shù)的時(shí)候,我們必須考慮其存儲(chǔ)的精度值。因此可以通過提高變量的精度值定義的方式來盡量縮減這種誤差。例如,我們將上例改為:
Main()
通過查閱《中國(guó)煤炭工業(yè)年鑒2008》統(tǒng)計(jì)出1984年至2008年我國(guó)煤礦百萬噸死亡率數(shù)據(jù)。根據(jù)對(duì)國(guó)家安全管理監(jiān)督總局(現(xiàn)改名為中華人民共和國(guó)應(yīng)急管理部)公報(bào)的搜集,整理出2009年至2017年我國(guó)煤礦百萬噸死亡率數(shù)據(jù)[7,8]。得出我國(guó)近30年煤礦百萬噸死亡率曲線走勢(shì)圖。如圖1所示。
{
double A;
A=(float)4/5;
Printf(“A=%10.8f\n”,A);
Return 0;
}
結(jié)果:A=0.80000000
分析:通過對(duì)于變量A的數(shù)據(jù)類型精度的提高,將其由float變?yōu)閐ouble,而雙精度數(shù)據(jù)(double)其存儲(chǔ)范圍則擴(kuò)大了許多,從32位變?yōu)榱?4位(見表2)。
表2 雙精度浮點(diǎn)型數(shù)據(jù)存儲(chǔ)格式表
Double的精度:2^52=4503599627370496,一共16位,1015<4503599627370496<1016這意味著最多能有16位有效數(shù)字,但絕對(duì)能保證的為15位,即double的精度為15~16位有效數(shù)字。這樣就可以保證在指定8位有效數(shù)字時(shí)的數(shù)據(jù)的精度。
同理,就算應(yīng)用雙精度,我們也需要注意指定的小數(shù)點(diǎn)后有效數(shù)位,不能夠超出其實(shí)際的有效位數(shù)。
另外,long double型數(shù)能提供的十進(jìn)制數(shù)的有效數(shù)字不超過19位,即精度為18~19。但由于C語言沒有去充分實(shí)現(xiàn),其實(shí)際能提供的數(shù)據(jù)的精度與double型相當(dāng)。
3.1.3 設(shè)定數(shù)據(jù)偏移量
我們還可以使用在程序中加設(shè)數(shù)據(jù)偏移量的方法來盡量規(guī)避這種由于數(shù)據(jù)存儲(chǔ)類型的限制而造成的誤差,假設(shè)我們?cè)O(shè)定數(shù)據(jù)偏移量為0.001,在printf程序語句前加上下面的控制程序:
if(A-0.800<0.001?5)
{
A=0.800;
}
else
{
A=0.800+0.001;}
通過程序來控制數(shù)據(jù)的精度,可以得出最終的結(jié)果A=0.80000000。
4.1 判斷兩個(gè)經(jīng)過運(yùn)算的浮點(diǎn)數(shù)相等
由于浮點(diǎn)數(shù)是采用二進(jìn)制科學(xué)計(jì)數(shù)法來進(jìn)行存儲(chǔ)的,因此,絕大多數(shù)的小數(shù)在計(jì)算機(jī)內(nèi)存中是不能精確表示的。
例如:如果兩個(gè)數(shù)x、y都是直接用常數(shù)賦值,這時(shí)我們判斷x,y是否相等。
Main()
float x=8.8,y=8.8;
if(x==y(tǒng))
{
printf("x=y(tǒng)\n");
}
輸出:x=y(tǒng)
如果我們將x或y改成經(jīng)過運(yùn)算后得到的值,這時(shí)我們?cè)賮砼袛鄕,y是否相等:
Main()
float x=4.4+4.4,y=8.8;
if(x==y(tǒng))
{
printf("x=y(tǒng)\n");
}
else
{
printf("x! =y(tǒng)\n");
}
輸出:x! =y(tǒng)從這個(gè)例子就可以發(fā)現(xiàn),C語言中浮點(diǎn)數(shù)的計(jì)算結(jié)果很多都是近似到浮點(diǎn)數(shù)的最大表示值來處理的,這樣一來就會(huì)造成誤差。因此,如果想判斷x是否等于y,應(yīng)該用兩數(shù)之差的絕對(duì)值和一個(gè)很小的數(shù)來比較,當(dāng)差值小于這個(gè)很小的數(shù)時(shí),我們就可以確定兩個(gè)數(shù)近似相等:fabs(x-y)<10-6。
4.2 浮點(diǎn)數(shù)作為循環(huán)變量
C語言中由于浮點(diǎn)數(shù)計(jì)算存在的誤差,可能會(huì)使循環(huán)次數(shù)達(dá)不到預(yù)定的次數(shù)而導(dǎo)致程序出現(xiàn)誤差,例如:
main()
{
float i j;
for(i=1,j=0;i< =10;i+ =0.1)
{
j+ =i;
}
printf(“j=%f”,j);
}
這個(gè)程序中,因?yàn)閕是浮點(diǎn)數(shù)計(jì)算取得值,會(huì)導(dǎo)致在循環(huán)到10的時(shí)候它的實(shí)際內(nèi)存存儲(chǔ)值變成10.000000001,這個(gè)值大于10,for語句會(huì)使程序循環(huán)終止,因此沒有達(dá)到預(yù)定的循環(huán)次數(shù),導(dǎo)致求和結(jié)果和我們本身想要的結(jié)果有誤差。
4.3 一個(gè)很大的浮點(diǎn)數(shù)加上一個(gè)比較小的浮點(diǎn)數(shù)
C語言中由于浮點(diǎn)數(shù)數(shù)據(jù)存儲(chǔ)有效位數(shù)的原因,可能會(huì)在計(jì)算一個(gè)很大數(shù)加上一個(gè)很小的數(shù)時(shí),小數(shù)計(jì)入大數(shù)累加時(shí)被溢出而造成誤差,例如:
Main()
{
float i,j,sum;
i=1000000;j=0.1
sum=i+j;
printf(“sum=%f\n”,sum);
return 0;
}
因?yàn)閒loat一共只有6~7位有效數(shù)字(10進(jìn)制),如果整數(shù)部分的位數(shù)多了,相應(yīng)的小數(shù)部分的精確顯示位數(shù)就少了,因此當(dāng)一個(gè)很大的數(shù)(如100000)去加一個(gè)很小的數(shù)(如0.1),那么小數(shù)部分的數(shù)值就會(huì)因?yàn)榇鎯?chǔ)溢出而丟失,從而產(chǎn)生誤差,得不到我們想要的理論值1000000.1,而是1000000.0。
C語言程序編寫時(shí),如果沒有很好地理解和掌握數(shù)據(jù)存儲(chǔ)和數(shù)據(jù)類型轉(zhuǎn)換的問題,那么在實(shí)際的編程中就會(huì)出現(xiàn)很多的誤差。特別是在做浮點(diǎn)型數(shù)據(jù)計(jì)算的過程中,由于內(nèi)存中存儲(chǔ)的方式多為乘2取整,所以計(jì)算機(jī)在精度范圍內(nèi)取舍時(shí)會(huì)導(dǎo)致數(shù)據(jù)出現(xiàn)誤差。在實(shí)際的編程中一定要注意C語言中浮點(diǎn)型數(shù)據(jù)的精度問題。
[1]張宗杰,張明亮.C語言中浮點(diǎn)數(shù)的存儲(chǔ)格式及其有效數(shù)字位數(shù)[J].計(jì)算機(jī)與數(shù)字工程,2006(1):84.
[2]杜叔強(qiáng).淺析C語言中的浮點(diǎn)數(shù)[J].蘭州工業(yè)高等??茖W(xué)校學(xué)報(bào),2010(5):26.
Analysis on Precision of Floating-point Number for C Language
ZHOU Guan?fang
(Organizational and Personnel Department,Yunyang Teachers’College,Shiyan 420000,China)
This article describes how data errors are caused in C language due to differentdata storagemethod with case study,an?alyses the reason for such errors and explains different storagemethods of floating point data in C language.Finally,some sugges?tions are proposed in this article.
C Language;data precision;floating point data;relative error
TP312
A
2095?8153(2015)03?0097?03
2015?05?15
周冠方(1984-),男,鄖陽師范高等專科學(xué)校組織人事部助教。